function createApplication(options) { var app = loopbackExpress(); merge(app, proto); app.loopback = loopback; // 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 var registry = app.registry = new Registry(); if (options && options.loadBuiltinModels === true) { require('./builtin-models')(registry); } } else { app.registry = loopback.registry; } return app; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function Conflict(modelId, SourceModel, TargetModel) { this.SourceModel = SourceModel; this.TargetModel = TargetModel; this.SourceChange = SourceModel.getChangeModel(); this.TargetChange = TargetModel.getChangeModel(); this.modelId = modelId; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function Connector(options) { EventEmitter.apply(this, arguments); this.options = options; debug('created with options', options); }
n/a
function DataSource(name, settings, modelBuilder) { if (!(this instanceof DataSource)) { return new DataSource(name, settings); } // Check if the settings object is passed as the first argument if (typeof name === 'object' && settings === undefined) { settings = name; name = undefined; } // Check if the first argument is a URL if (typeof name === 'string' && name.indexOf('://') !== -1) { name = utils.parseSettings(name); } // Check if the settings is in the form of URL string if (typeof settings === 'string' && settings.indexOf('://') !== -1) { settings = utils.parseSettings(settings); } this.modelBuilder = modelBuilder || new ModelBuilder(); this.models = this.modelBuilder.models; this.definitions = this.modelBuilder.definitions; this.juggler = juggler; // operation metadata // Initialize it before calling setup as the connector might register operations this._operations = {}; this.setup(name, settings); this._setupConnector(); // connector var connector = this.connector; // DataAccessObject - connector defined or supply the default var dao = (connector && connector.DataAccessObject) || this.constructor.DataAccessObject; this.DataAccessObject = function() { }; // define DataAccessObject methods Object.keys(dao).forEach(function(name) { var fn = dao[name]; this.DataAccessObject[name] = fn; if (typeof fn === 'function') { this.defineOperation(name, { accepts: fn.accepts, 'returns': fn.returns, http: fn.http, remoteEnabled: fn.shared ? true : false, scope: this.DataAccessObject, fnName: name, }); } }.bind(this)); // define DataAccessObject.prototype methods Object.keys(dao.prototype).forEach(function(name) { var fn = dao.prototype[name]; this.DataAccessObject.prototype[name] = fn; if (typeof fn === 'function') { this.defineOperation(name, { prototype: true, accepts: fn.accepts, 'returns': fn.returns, http: fn.http, remoteEnabled: fn.shared ? true : false, scope: this.DataAccessObject.prototype, fnName: name, }); } }.bind(this)); }
n/a
function Any(value) { if (!(this instanceof Any)) { return value; } this.value = value; }
n/a
function DataAccessObject() { if (DataAccessObject._mixins) { var self = this; var args = arguments; DataAccessObject._mixins.forEach(function(m) { m.call(self, args); }); } }
n/a
function JSON(value) { if (!(this instanceof JSON)) { return value; } this.value = value; }
n/a
function Text(value) { if (!(this instanceof Text)) { return value; } this.value = value; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function GeoPoint(data) { if (!(this instanceof GeoPoint)) { return new GeoPoint(data); } if (arguments.length === 2) { data = { lat: arguments[0], lng: arguments[1], }; } assert(Array.isArray(data) || typeof data === 'object' || typeof data === 'string', 'must provide valid geo-coordinates array [lat, lng] or object or a "lat, lng" string'); if (typeof data === 'string') { try { data = JSON.parse(data); } catch (err) { data = data.split(/,\s*/); assert(data.length === 2, 'must provide a string "lat,lng" creating a GeoPoint with a string'); } } if (Array.isArray(data)) { data = { lat: Number(data[0]), lng: Number(data[1]), }; } else { data.lng = Number(data.lng); data.lat = Number(data.lat); } assert(typeof data === 'object', 'must provide a lat and lng object when creating a GeoPoint'); assert(typeof data.lat === 'number' && !isNaN(data.lat), 'lat must be a number when creating a GeoPoint'); assert(typeof data.lng === 'number' && !isNaN(data.lng), 'lng must be a number when creating a GeoPoint'); assert(data.lng <= 180, 'lng must be <= 180'); assert(data.lng >= -180, 'lng must be >= -180'); assert(data.lat <= 90, 'lat must be <= 90'); assert(data.lat >= -90, 'lat must be >= -90'); this.lat = data.lat; this.lng = data.lng; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function MailConnector(settings) { assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object'); var transports = settings.transports; // if transports is not in settings object AND settings.transport exists if (!transports && settings.transport) { // then wrap single transport in an array and assign to transports transports = [settings.transport]; } if (!transports) { transports = []; } this.transportsIndex = {}; this.transports = []; if (loopback.isServer) { transports.forEach(this.setupTransport.bind(this)); } }
n/a
function Mailer() { }
n/a
function Memory() { // TODO implement entire memory connector }
n/a
function RemoteConnector(settings) { assert(typeof settings === 'object', 'cannot initiaze RemoteConnector without a settings object'); this.client = settings.client; this.adapter = settings.adapter || 'rest'; this.protocol = settings.protocol || 'http'; this.root = settings.root || ''; this.host = settings.host || 'localhost'; this.port = settings.port || 3000; this.remotes = remoting.create(); this.name = 'remote-connector'; if (settings.url) { this.url = settings.url; } else { this.url = this.protocol + '://' + this.host + ':' + this.port + this.root; } // handle mixins in the define() method var DAO = this.DataAccessObject = function() { }; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function Route(path) { this.path = path; this.stack = []; debug('new %o', path) // route handlers for various http methods this.methods = {}; }
n/a
Router = function (options) { var opts = options || {}; function router(req, res, next) { router.handle(req, res, next); } // mixin Router class functions setPrototypeOf(router, proto) router.params = {}; router._params = []; router.caseSensitive = opts.caseSensitive; router.mergeParams = opts.mergeParams; router.strict = opts.strict; router.stack = []; return router; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function Error() { [native code] }
n/a
configureModel = function (ModelCtor, config) { return this.registry.configureModel.apply(this.registry, arguments); }
...
}
config = extend({}, config);
config.dataSource = dataSource;
setSharedMethodSharedProperties(ModelCtor, app, config);
app.registry.configureModel(ModelCtor, config);
}
function setSharedMethodSharedProperties(model, app, modelConfigs) {
var settings = {};
// apply config.json settings
var config = app.get('remoting');
...
context = function () { throw new Error(g.f( '%s middleware was removed in version 3.0. See %s for more details.', 'loopback#context', 'http://loopback.io/doc/en/lb2/Using-current-context.html')); }
n/a
createContext = function (scopeName) { throw new Error(g.f( '%s was removed in version 3.0. See %s for more details.', 'loopback.createContext()', 'http://loopback.io/doc/en/lb2/Using-current-context.html')); }
...
'loopback.runInContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
};
loopback.createContext = function(scopeName) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.createContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
};
};
...
createDataSource = function (name, options) { return this.registry.createDataSource.apply(this.registry, arguments); }
...
config.connector = require(connectorPath);
}
}
if (config.connector && typeof config.connector === 'object' && !config.connector.name)
config.connector.name = name;
}
return registry.createDataSource(config);
}
function configureModel(ModelCtor, config, app) {
assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'),
ModelCtor.modelName + ' must be a descendant of loopback.Model');
var dataSource = config.dataSource;
...
createModel = function (name, properties, options) { return this.registry.createModel.apply(this.registry, arguments); }
...
app.model = function(Model, config) {
var isPublic = true;
var registry = this.registry;
if (typeof Model === 'string') {
var msg = 'app.model(modelName, settings) is no longer supported. ' +
'Use app.registry.createModel(modelName, definition) and ' +
'app.model(ModelCtor, config) instead.';
throw new Error(msg);
}
if (arguments.length > 1) {
config = config || {};
configureModel(Model, config, this);
...
errorHandler = function (options) { throw new Error('loopback.errorHandler is no longer available.' + ' Please use the module "strong-error-handler" instead.'); }
n/a
favicon = function (icon, options) { icon = icon || path.join(__dirname, '../../favicon.ico'); return favicon(icon, options); }
...
'use strict';
var favicon = require('serve-favicon');
var path = require('path');
/**
* Serve the LoopBack favicon.
* @header loopback.favicon()
*/
module.exports = function(icon, options) {
icon = icon || path.join(__dirname, '../../favicon.ico');
return favicon(icon, options);
};
...
findModel = function (modelName) { return this.registry.findModel.apply(this.registry, arguments); }
...
// the principalType must either be 'USER'
if (p.type === Principal.USER) {
return {id: p.id, principalType: p.type};
}
// or permit to resolve a valid user model
var userModel = this.registry.findModel(p.type);
if (!userModel) continue;
if (userModel.prototype instanceof BaseUser) {
return {id: p.id, principalType: p.type};
}
}
};
...
getCurrentContext = function () { throw new Error(g.f( '%s was removed in version 3.0. See %s for more details.', 'loopback.getCurrentContext()', 'http://loopback.io/doc/en/lb2/Using-current-context.html')); }
...
module.exports = function(loopback) {
juggler.getCurrentContext =
remoting.getCurrentContext =
loopback.getCurrentContext = function() {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.getCurrentContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
};
loopback.runInContext = function(fn) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.runInContext()',
...
getModel = function (modelName) { return this.registry.getModel.apply(this.registry, arguments); }
...
context = context || {};
assert(context.registry,
'Application registry is mandatory in AccessContext but missing in provided context');
this.registry = context.registry;
this.principals = context.principals || [];
var model = context.model;
model = ('string' === typeof model) ? this.registry.getModel(model) : model
;
this.model = model;
this.modelName = model && model.modelName;
this.modelId = context.id || context.modelId;
this.property = context.property || AccessContext.ALL;
this.method = context.method;
...
getModelByType = function (modelType) { return this.registry.getModelByType.apply(this.registry, arguments); }
...
// The function is used as a setter
_aclModel = ACL;
}
if (_aclModel) {
return _aclModel;
}
var aclModel = registry.getModel('ACL');
_aclModel = registry.getModelByType(aclModel);
return _aclModel;
};
/**
* Check if the given access token can invoke the specified method.
*
* @param {AccessToken} token The access token.
...
function createApplication(options) { var app = loopbackExpress(); merge(app, proto); app.loopback = loopback; // 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 var registry = app.registry = new Registry(); if (options && options.loadBuiltinModels === true) { require('./builtin-models')(registry); } } else { app.registry = loopback.registry; } return app; }
n/a
memory = function (name) { return this.registry.memory.apply(this.registry, arguments); }
n/a
function query(options) { var opts = Object.create(options || null); var queryparse = qs.parse; if (typeof options === 'function') { queryparse = options; opts = undefined; } if (opts !== undefined && opts.allowPrototypes === undefined) { // back-compat for qs module opts.allowPrototypes = true; } return function query(req, res, next){ if (!req.query) { var val = parseUrl(req).query; req.query = queryparse(val, opts); } next(); }; }
n/a
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'}; }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function rest() { var handlers; // Cached handlers return function restApiHandler(req, res, next) { var app = req.app; var registry = app.registry; if (!handlers) { handlers = []; var remotingOptions = app.get('remoting') || {}; var contextOptions = remotingOptions.context; if (contextOptions !== undefined && contextOptions !== false) { throw new Error(g.f( '%s was removed in version 3.0. See %s for more details.', 'remoting.context option', 'http://loopback.io/doc/en/lb2/Using-current-context.html')); } if (app.isAuthEnabled) { var AccessToken = registry.getModelByType('AccessToken'); handlers.push(loopback.token({model: AccessToken, app: app})); } handlers.push(function(req, res, next) { // Need to get an instance of the REST handler per request return app.handler('rest')(req, res, next); }); } if (handlers.length === 1) { return handlers[0](req, res, next); } async.eachSeries(handlers, function(handler, done) { handler(req, res, done); }, next); }; }
...
module.exports = rest;
/**
* Expose models over REST.
*
* For example:
* ```js
* app.use(loopback.rest());
* ```
* For more information, see [Exposing models over a REST API](http://loopback.io/doc/en/lb2/Exposing-models-over-REST.html).
* @header loopback.rest()
*/
function rest() {
var handlers; // Cached handlers
...
runInContext = function (fn) { throw new Error(g.f( '%s was removed in version 3.0. See %s for more details.', 'loopback.runInContext()', 'http://loopback.io/doc/en/lb2/Using-current-context.html')); }
...
'loopback.getCurrentContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
};
loopback.runInContext = function(fn) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.runInContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
};
loopback.createContext = function(scopeName) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.createContext()',
...
function serveStatic(root, options) { if (!root) { throw new TypeError('root path required') } if (typeof root !== 'string') { throw new TypeError('root path must be a string') } // copy options object var opts = Object.create(options || null) // fall-though var fallthrough = opts.fallthrough !== false // default redirect var redirect = opts.redirect !== false // headers listener var setHeaders = opts.setHeaders if (setHeaders && typeof setHeaders !== 'function') { throw new TypeError('option setHeaders must be function') } // setup options for send opts.maxage = opts.maxage || opts.maxAge || 0 opts.root = resolve(root) // construct directory listener var onDirectory = redirect ? createRedirectDirectoryListener() : createNotFoundDirectoryListener() return function serveStatic (req, res, next) { if (req.method !== 'GET' && req.method !== 'HEAD') { if (fallthrough) { return next() } // method not allowed res.statusCode = 405 res.setHeader('Allow', 'GET, HEAD') res.setHeader('Content-Length', '0') res.end() return } var forwardError = !fallthrough var originalUrl = parseUrl.original(req) var path = parseUrl(req).pathname // make sure redirect occurs at mount if (path === '/' && originalUrl.pathname.substr(-1) !== '/') { path = '' } // create send stream var stream = send(req, path, opts) // add directory handler stream.on('directory', onDirectory) // add headers listener if (setHeaders) { stream.on('headers', setHeaders) } // add file listener for fallthrough if (fallthrough) { stream.on('file', function onFile () { // once file is determined, always forward error forwardError = true }) } // forward errors stream.on('error', function error (err) { if (forwardError || !(err.statusCode < 500)) { next(err) return } next() }) // pipe stream.pipe(res) } }
...
* Serve static assets of a LoopBack application.
*
* @param {string} root The root directory from which the static assets are to
* be served.
* @param {object} options Refer to
* [express documentation](http://expressjs.com/4x/api.html#express.static)
* for the full list of available options.
* @header loopback.static(root, [options])
*/
'use strict';
module.exports = require('express').static;
...
function status() { var started = new Date(); return function(req, res) { res.send({ started: started, uptime: (Date.now() - Number(started)) / 1000, }); }; }
...
UserModel.afterRemote('confirm', function(ctx, inst, next) {
if (ctx.args.redirect !== undefined) {
if (!ctx.res) {
return next(new Error(g.f('The transport does not support HTTP redirects.')));
}
ctx.res.location(ctx.args.redirect);
ctx.res.status(302);
}
next();
});
// default models
assert(loopback.Email, 'Email model must be defined before User model');
UserModel.email = loopback.Email;
...
template = function (file) { var templates = this._templates || (this._templates = {}); var str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8')); return ejs.compile(str, { filename: file, }); }
...
});
}
}
return fn.promise;
};
function createVerificationEmailBody(options, cb) {
var template = loopback.template(options.template);
var body = template(options);
cb(null, body);
}
/**
* A default verification token generator which accepts the user the token is
* being generated for and a callback function to indicate completion.
...
function token(options) { options = options || {}; var TokenModel; var currentUserLiteral = options.currentUserLiteral; if (currentUserLiteral && (typeof currentUserLiteral !== 'string')) { debug('Set currentUserLiteral to \'me\' as the value is not a string.'); currentUserLiteral = 'me'; } if (typeof currentUserLiteral === 'string') { currentUserLiteral = escapeRegExp(currentUserLiteral); } var enableDoublecheck = !!options.enableDoublecheck; var overwriteExistingToken = !!options.overwriteExistingToken; return function(req, res, next) { var app = req.app; var registry = app.registry; if (!TokenModel) { TokenModel = registry.getModel(options.model || 'AccessToken'); } assert(typeof TokenModel === 'function', 'loopback.token() middleware requires a AccessToken model'); if (req.accessToken !== undefined) { if (!enableDoublecheck) { // req.accessToken is defined already (might also be "null" or "false") and enableDoublecheck // has not been set --> skip searching for credentials rewriteUserLiteral(req, currentUserLiteral); return next(); } if (req.accessToken && req.accessToken.id && !overwriteExistingToken) { // req.accessToken.id is defined, which means that some other middleware has identified a valid user. // when overwriteExistingToken is not set to a truthy value, skip searching for credentials. rewriteUserLiteral(req, currentUserLiteral); return next(); } // continue normal operation (as if req.accessToken was undefined) } TokenModel.findForRequest(req, options, function(err, token) { req.accessToken = token || null; rewriteUserLiteral(req, currentUserLiteral); var ctx = req.loopbackContext; if (ctx && ctx.active) ctx.set('accessToken', token); next(err); }); }; }
...
'%s was removed in version 3.0. See %s for more details.',
'remoting.context option',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
}
if (app.isAuthEnabled) {
var AccessToken = registry.getModelByType('AccessToken');
handlers.push(loopback.token({model: AccessToken, app: app}));
}
handlers.push(function(req, res, next) {
// Need to get an instance of the REST handler per request
return app.handler('rest')(req, res, next);
});
}
...
function urlNotFound() { return function raiseUrlNotFoundError(req, res, next) { var error = new Error('Cannot ' + req.method + ' ' + req.url); error.status = 404; next(error); }; }
...
*/
'use strict';
module.exports = urlNotFound;
/**
* Convert any request not handled so far to a 404 error
* to be handled by error-handling middleware.
* @header loopback.urlNotFound()
*/
function urlNotFound() {
return function raiseUrlNotFoundError(req, res, next) {
var error = new Error('Cannot ' + req.method + ' ' + req.url);
error.status = 404;
next(error);
};
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_defineChangeModel = function () { var BaseChangeModel = this.registry.getModel('Change'); assert(BaseChangeModel, 'Change model must be defined before enabling change replication'); const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {}; this.Change = BaseChangeModel.extend(this.modelName + '-change', additionalChangeModelProperties, {trackModel: this} ); if (this.dataSource) { attachRelatedModels(this); } // Re-attach related models whenever our datasource is changed. var self = this; this.on('dataSourceAttached', function() { attachRelatedModels(self); }); return this.Change; function attachRelatedModels(self) { self.Change.attachTo(self.dataSource); self.Change.getCheckpointModel().attachTo(self.dataSource); } }
...
// call Model.setup first
Model.setup.call(this);
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
bulkUpdate = function (updates, options, callback) { var tasks = []; var Model = this; var Change = this.getChangeModel(); var conflicts = []; var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof options === 'function') { options = {}; } options = options || {}; buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) { if (err) return callback(err); updates.forEach(function(update) { var id = update.change.modelId; var current = currentMap[id]; switch (update.type) { case Change.UPDATE: tasks.push(function(cb) { applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.CREATE: tasks.push(function(cb) { applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.DELETE: tasks.push(function(cb) { applyDelete(Model, id, current, update.change, conflicts, options, cb); }); break; } }); async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = {conflicts: conflicts}; return callback(err); } callback(); }); }); }
...
function bulkUpdate(_updates, cb) {
debug('\tstarting bulk update');
updates = _updates;
utils.uploadInChunks(
updates,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.bulkUpdate(smallArray, options, function(err) {
// bulk update is a special case where we want to process all chunks and aggregate all errors
chunkCallback(null, err);
});
},
function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) {
...
changes = function (since, filter, callback) { if (typeof since === 'function') { filter = {}; callback = since; since = -1; } if (typeof filter === 'function') { callback = filter; since = -1; filter = {}; } var idName = this.dataSource.idName(this.modelName); var Change = this.getChangeModel(); var model = this; const changeFilter = this.createChangeFilter(since, filter); filter = filter || {}; filter.fields = {}; filter.where = filter.where || {}; filter.fields[idName] = true; // TODO(ritch) this whole thing could be optimized a bit more Change.find(changeFilter, function(err, changes) { if (err) return callback(err); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); var ids = changes.map(function(change) { return change.getModelId(); }); filter.where[idName] = {inq: ids}; model.find(filter, function(err, models) { if (err) return callback(err); var modelIds = models.map(function(m) { return m[idName].toString(); }); callback(null, changes.filter(function(ch) { if (ch.type() === Change.DELETE) return true; return modelIds.indexOf(ch.modelId) > -1; })); }); }); }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
checkAccessForContext = function (context, callback) { if (!callback) callback = utils.createPromiseCallback(); var self = this; self.resolveRelatedModels(); var roleModel = self.roleModel; if (!(context instanceof AccessContext)) { context.registry = this.registry; context = new AccessContext(context); } var authorizedRoles = {}; var remotingContext = context.remotingContext; var model = context.model; var modelDefaultPermission = model && model.settings.defaultPermission; var property = context.property; var accessType = context.accessType; var modelName = context.modelName; var methodNames = context.methodNames; var propertyQuery = (property === ACL.ALL) ? undefined : {inq: methodNames.concat([ACL.ALL])}; var accessTypeQuery = (accessType === ACL.ALL) ? undefined : (accessType === ACL.REPLICATE) ? {inq: [ACL.REPLICATE, ACL.WRITE, ACL.ALL]} : {inq: [accessType, ACL.ALL]}; var req = new AccessRequest({ model: modelName, property, accessType, permission: ACL.DEFAULT, methodNames, registry: this.registry}); var effectiveACLs = []; var staticACLs = self.getStaticACLs(model.modelName, property); this.find({where: {model: model.modelName, property: propertyQuery, accessType: accessTypeQuery}}, function(err, acls) { if (err) return callback(err); var inRoleTasks = []; acls = acls.concat(staticACLs); acls.forEach(function(acl) { // Check exact matches for (var i = 0; i < context.principals.length; i++) { var p = context.principals[i]; var typeMatch = p.type === acl.principalType; var idMatch = String(p.id) === String(acl.principalId); if (typeMatch && idMatch) { effectiveACLs.push(acl); return; } } // Check role matches if (acl.principalType === ACL.ROLE) { inRoleTasks.push(function(done) { roleModel.isInRole(acl.principalId, context, function(err, inRole) { if (!err && inRole) { effectiveACLs.push(acl); // add the role to authorizedRoles if allowed if (acl.isAllowed(modelDefaultPermission)) authorizedRoles[acl.principalId] = true; } done(err, acl); }); }); } }); async.parallel(inRoleTasks, function(err, results) { if (err) return callback(err, null); // resolved is an instance of AccessRequest var resolved = self.resolvePermission(effectiveACLs, req); debug('---Resolved---'); resolved.debug(); // set authorizedRoles in remotingContext options argument if // resolved AccessRequest permission is ALLOW, else set it to empty object authorizedRoles = resolved.isAllowed() ? authorizedRoles : {}; saveAuthorizedRolesToRemotingContext(remotingContext, authorizedRoles); return callback(null, resolved); }); }); return callback.promise; }
...
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
aclModel.checkAccessForContext({
accessToken: token,
model: this,
property: sharedMethod.name,
method: sharedMethod.name,
sharedMethod: sharedMethod,
modelId: modelId,
accessType: this._getAccessTypeForMethod(sharedMethod),
...
checkAccessForToken = function (token, model, modelId, method, callback) { assert(token, 'Access token is required'); if (!callback) callback = utils.createPromiseCallback(); var context = new AccessContext({ registry: this.registry, accessToken: token, model: model, property: method, method: method, modelId: modelId, }); this.checkAccessForContext(context, function(err, accessRequest) { if (err) callback(err); else callback(null, accessRequest.isAllowed()); }); return callback.promise; }
n/a
function checkPermission(principalType, principalId, model, property, accessType, callback) { if (!callback) callback = utils.createPromiseCallback(); if (principalId !== null && principalId !== undefined && (typeof principalId !== 'string')) { principalId = principalId.toString(); } property = property || ACL.ALL; var propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]}; accessType = accessType || ACL.ALL; var accessTypeQuery = (accessType === ACL.ALL) ? undefined : {inq: [accessType, ACL.ALL, ACL.EXECUTE]}; var req = new AccessRequest({model, property, accessType, registry: this.registry}); var acls = this.getStaticACLs(model, property); // resolved is an instance of AccessRequest var resolved = this.resolvePermission(acls, req); if (resolved && resolved.permission === ACL.DENY) { debug('Permission denied by statically resolved permission'); debug(' Resolved Permission: %j', resolved); process.nextTick(function() { callback(null, resolved); }); return callback.promise; } var self = this; this.find({where: {principalType: principalType, principalId: principalId, model: model, property: propertyQuery, accessType: accessTypeQuery}}, function(err, dynACLs) { if (err) { return callback(err); } acls = acls.concat(dynACLs); // resolved is an instance of AccessRequest resolved = self.resolvePermission(acls, req); return callback(null, resolved); }); return callback.promise; }
...
assert(aclModel,
'ACL model must be defined before Scope.checkPermission is called');
this.findOne({where: {name: scope}}, function(err, scope) {
if (err) {
if (callback) callback(err);
} else {
aclModel.checkPermission(
aclModel.SCOPE, scope.id, model, property, accessType, callback);
}
});
};
};
...
checkpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.bumpLastSeq(cb); }
...
}
cb(err);
});
}
function checkpoints() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) {
if (err) return cb(err);
newSourceCp = source.seq;
targetModel.checkpoint(function(err, target) {
if (err) return cb(err);
newTargetCp = target.seq;
debug('\tcreated checkpoints');
debug('\t\t%s for source model %s', newSourceCp, sourceModel.modelName);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
count = function (where, cb) { throwNotAttached(this.modelName, 'count'); }
n/a
create = function (data, callback) { throwNotAttached(this.modelName, 'create'); }
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
createChangeFilter = function (since, modelFilter) { return { where: { checkpoint: {gte: since}, modelName: this.modelName, }, }; }
...
since = -1;
filter = {};
}
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
var model = this;
const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
...
createChangeStream = function (options, cb) { if (typeof options === 'function') { cb = options; options = undefined; } var idName = this.getIdName(); var Model = this; var changes = new PassThrough({objectMode: true}); var writeable = true; changes.destroy = function() { changes.removeAllListeners('error'); changes.removeAllListeners('end'); writeable = false; changes = null; }; changes.on('error', function() { writeable = false; }); changes.on('end', function() { writeable = false; }); process.nextTick(function() { cb(null, changes); }); Model.observe('after save', createChangeHandler('save')); Model.observe('after delete', createChangeHandler('delete')); function createChangeHandler(type) { return function(ctx, next) { // since it might have set to null via destroy if (!changes) { return next(); } var where = ctx.where; var data = ctx.instance || ctx.data; var whereId = where && where[idName]; // the data includes the id // or the where includes the id var target; if (data && (data[idName] || data[idName] === 0)) { target = data[idName]; } else if (where && (where[idName] || where[idName] === 0)) { target = where[idName]; } var hasTarget = target === 0 || !!target; var change = { target: target, where: where, data: data, }; switch (type) { case 'save': if (ctx.isNewInstance === undefined) { change.type = hasTarget ? 'update' : 'create'; } else { change.type = ctx.isNewInstance ? 'create' : 'update'; } break; case 'delete': change.type = 'remove'; break; } // TODO(ritch) this is ugly... maybe a ReadableStream would be better if (writeable) { changes.write(change); } next(); }; } }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
createUpdates = function (deltas, cb) { var Change = this.getChangeModel(); var updates = []; var Model = this; var tasks = []; deltas.forEach(function(change) { change = new Change(change); var type = change.type(); var update = {type: type, change: change}; switch (type) { case Change.CREATE: case Change.UPDATE: tasks.push(function(cb) { Model.findById(change.modelId, function(err, inst) { if (err) return cb(err); if (!inst) { return cb && cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); } else { update.data = inst; } updates.push(update); cb(); }); }); break; case Change.DELETE: updates.push(update); break; } }); async.parallel(tasks, function(err) { if (err) return cb(err); cb(null, updates); }); }
...
if (diff && diff.deltas && diff.deltas.length) {
debug('\tbuilding a list of updates');
utils.uploadInChunks(
diff.deltas,
replicationChunkSize,
function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback);
},
cb);
} else {
// nothing to replicate
done();
}
}
...
currentCheckpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.current(cb); }
n/a
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
diff = function (since, remoteChanges, callback) { var Change = this.getChangeModel(); Change.diff(this.modelName, since, remoteChanges, callback); }
...
},
});
};
/**
* Get a set of deltas and conflicts since the given checkpoint.
*
* See [Change.diff()](#change-diff) for details.
*
* @param {Number} since Find deltas since this checkpoint.
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
...
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
enableChangeTracking = function () { var Model = this; var Change = this.Change || this._defineChangeModel(); var cleanupInterval = Model.settings.changeCleanupInterval || 30000; assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + ' is not attached to a dataSource'); var idName = this.getIdName(); var idProp = this.definition.properties[idName]; var idType = idProp && idProp.type; var idDefn = idProp && idProp.defaultFn; if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { deprecated('The model ' + this.modelName + ' is tracking changes, ' + 'which requires a string id with GUID/UUID default value.'); } Model.observe('after save', rectifyOnSave); Model.observe('after delete', rectifyOnDelete); // Only run if the run time is server // Can switch off cleanup by setting the interval to -1 if (runtime.isServer && cleanupInterval > 0) { // initial cleanup cleanup(); // cleanup setInterval(cleanup, cleanupInterval); } function cleanup() { Model.rectifyAllChanges(function(err) { if (err) { Model.handleChangeError(err, 'cleanup'); } }); } }
...
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
...
eventNames = function () { [native code] }
n/a
function exists(id, cb) { throwNotAttached(this.modelName, 'exists'); }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
function find(filter, cb) { throwNotAttached(this.modelName, 'find'); }
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, cb) { throwNotAttached(this.modelName, 'findById'); }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.ACL.findById %s', id));
err.statusCode = 404;
...
findLastChange = function (id, cb) { var Change = this.getChangeModel(); Change.findOne({where: {modelId: id}}, cb); }
...
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
...
function findOne(filter, cb) { throwNotAttached(this.modelName, 'findOne'); }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, callback) { throwNotAttached(this.modelName, 'findOrCreate'); }
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getChangeModel = function () { var changeModel = this.Change; var isSetup = changeModel && changeModel.dataSource; assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); return changeModel; }
...
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback);
};
/**
* Get the changes to a model since the specified checkpoint. Provide a filter object
* to reduce the number of results returned.
* @param {Number} since Return only changes since this checkpoint.
...
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getIdName = function () { var Model = this; var ds = Model.getDataSource(); if (ds.idName) { return ds.idName(Model.modelName); } else { return 'id'; } }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
function getMatchingScore(rule, req) { var props = ['model', 'property', 'accessType']; var score = 0; for (var i = 0; i < props.length; i++) { // Shift the score by 4 for each of the properties as the weight score = score * 4; var ruleValue = rule[props[i]] || ACL.ALL; var requestedValue = req[props[i]] || ACL.ALL; var isMatchingMethodName = props[i] === 'property' && req.methodNames.indexOf(ruleValue) !== -1; var isMatchingAccessType = ruleValue === requestedValue; if (props[i] === 'accessType' && !isMatchingAccessType) { switch (ruleValue) { case ACL.EXECUTE: // EXECUTE should match READ, REPLICATE and WRITE isMatchingAccessType = true; break; case ACL.WRITE: // WRITE should match REPLICATE too isMatchingAccessType = requestedValue === ACL.REPLICATE; break; } } if (isMatchingMethodName || isMatchingAccessType) { // Exact match score += 3; } else if (ruleValue === ACL.ALL) { // Wildcard match score += 2; } else if (requestedValue === ACL.ALL) { score += 1; } else { // Doesn't match at all return -1; } } // Weigh against the principal type into 4 levels // - user level (explicitly allow/deny a given user) // - app level (explicitly allow/deny a given app) // - role level (role based authorization) // - other // user > app > role > ... score = score * 4; switch (rule.principalType) { case ACL.USER: score += 4; break; case ACL.APP: score += 3; break; case ACL.ROLE: score += 2; break; default: score += 1; } // Weigh against the roles // everyone < authenticated/unauthenticated < related < owner < ... score = score * 8; if (rule.principalType === ACL.ROLE) { switch (rule.principalId) { case Role.OWNER: score += 4; break; case Role.RELATED: score += 3; break; case Role.AUTHENTICATED: case Role.UNAUTHENTICATED: score += 2; break; case Role.EVERYONE: score += 1; break; default: score += 5; } } score = score * 4; score += AccessContext.permissionOrder[rule.permission || ACL.ALLOW] - 1; return score; }
...
/**
* Get matching score for the given `AccessRequest`.
* @param {AccessRequest} req The request
* @returns {Number} score
*/
ACL.prototype.score = function(req) {
return this.constructor.getMatchingScore(this, req);
};
/*!
* Resolve permission from the ACLs
* @param {Object[]) acls The list of ACLs
* @param {AccessRequest} req The access request
* @returns {AccessRequest} result The resolved access request
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
getSourceId = function (cb) { var dataSource = this.dataSource; if (!dataSource) { this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); } assert( dataSource.connector.name, 'Model.getSourceId: cannot get id without dataSource.connector.name' ); var id = [dataSource.connector.name, this.modelName].join('-'); cb(null, id); }
n/a
function getStaticACLs(model, property) { var modelClass = this.registry.findModel(model); var staticACLs = []; if (modelClass && modelClass.settings.acls) { modelClass.settings.acls.forEach(function(acl) { var prop = acl.property; // We support static ACL property with array of string values. if (Array.isArray(prop) && prop.indexOf(property) >= 0) prop = property; if (!prop || prop === ACL.ALL || property === prop) { staticACLs.push(new ACL({ model: model, property: prop || ACL.ALL, principalType: acl.principalType, principalId: acl.principalId, // TODO: Should it be a name? accessType: acl.accessType || ACL.ALL, permission: acl.permission, })); } }); } var prop = modelClass && ( // regular property modelClass.definition.properties[property] || // relation/scope (modelClass._scopeMeta && modelClass._scopeMeta[property]) || // static method modelClass[property] || // prototype method modelClass.prototype[property]); if (prop && prop.acls) { prop.acls.forEach(function(acl) { staticACLs.push(new ACL({ model: modelClass.modelName, property: property, principalType: acl.principalType, principalId: acl.principalId, accessType: acl.accessType, permission: acl.permission, })); }); } return staticACLs; }
...
var propertyQuery = (property === ACL.ALL) ? undefined : {inq: [property, ACL.ALL]};
accessType = accessType || ACL.ALL;
var accessTypeQuery = (accessType === ACL.ALL) ? undefined :
{inq: [accessType, ACL.ALL, ACL.EXECUTE]};
var req = new AccessRequest({model, property, accessType, registry: this.registry});
var acls = this.getStaticACLs(model, property);
// resolved is an instance of AccessRequest
var resolved = this.resolvePermission(acls, req);
if (resolved && resolved.permission === ACL.DENY) {
debug('Permission denied by statically resolved permission');
debug(' Resolved Permission: %j', resolved);
...
handleChangeError = function (err, operationName) { if (!err) return; this.emit('error', err, operationName); }
...
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance;
...
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
isAllowed = function (permission, defaultPermission) { if (permission === ACL.DEFAULT) { permission = defaultPermission || ACL.ALLOW; } return permission !== loopback.ACL.DENY; }
...
if (debug.enabled) {
debug('---AccessRequest---');
debug(' model %s', this.model);
debug(' property %s', this.property);
debug(' accessType %s', this.accessType);
debug(' permission %s', this.permission);
debug(' isWildcard() %s', this.isWildcard());
debug(' isAllowed() %s', this.isAllowed());
}
};
module.exports.AccessContext = AccessContext;
module.exports.Principal = Principal;
module.exports.AccessRequest = AccessRequest;
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isMappedToRole = function (principalType, principalId, role, cb) { cb = cb || utils.createPromiseCallback(); var self = this; this.resolvePrincipal(principalType, principalId, function(err, principal) { if (err) return cb(err); if (principal != null) { principalId = principal.id; } principalType = principalType || 'ROLE'; self.resolvePrincipal('ROLE', role, function(err, role) { if (err || !role) return cb(err, role); self.roleMappingModel.findOne({ where: { roleId: role.id, principalType: principalType, principalId: String(principalId), }, }, function(err, result) { if (err) return cb(err); return cb(null, !!result); }); }); }); return cb.promise; }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
n/a
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
rectifyAllChanges = function (callback) { this.getChangeModel().rectifyAll(callback); }
...
cleanup();
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
...
rectifyChange = function (id, callback) { var Change = this.getChangeModel(); Change.rectifyModelChanges(this.modelName, [id], callback); }
...
debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),
ctx.Model.modelName, id ? id : 'ALL');
debug('context instance:%j currentInstance:%j where:%j data %j',
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
}
if (id) {
ctx.Model.rectifyChange(id, reportErrorAndNext);
} else {
ctx.Model.rectifyAllChanges(reportErrorAndNext);
}
function reportErrorAndNext(err) {
if (err) {
ctx.Model.handleChangeError(err, 'after save');
...
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
n/a
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
function replaceById(id, data, cb) { throwNotAttached(this.modelName, 'replaceById'); }
n/a
function replaceOrCreate(data, callback) { throwNotAttached(this.modelName, 'replaceOrCreate'); }
n/a
replicate = function (since, targetModel, options, callback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof since === 'function' && since.modelName) { targetModel = since; since = -1; } if (typeof since !== 'object') { since = {source: since, target: since}; } if (typeof options === 'function') { options = {}; } options = options || {}; var sourceModel = this; callback = callback || utils.createPromiseCallback(); debug('replicating %s since %s to %s since %s', sourceModel.modelName, since.source, targetModel.modelName, since.target); if (options.filter) { debug('\twith filter %j', options.filter); } // In order to avoid a race condition between the replication and // other clients modifying the data, we must create the new target // checkpoint as the first step of the replication process. // As a side-effect of that, the replicated changes are associated // with the new target checkpoint. This is actually desired behaviour, // because that way clients replicating *from* the target model // since the new checkpoint will pick these changes up. // However, it increases the likelihood of (false) conflicts being detected. // In order to prevent that, we run the replication multiple times, // until no changes were replicated, but at most MAX_ATTEMPTS times // to prevent starvation. In most cases, the second run will find no changes // to replicate and we are done. var MAX_ATTEMPTS = 3; run(1, since); return callback.promise; function run(attempt, since) { debug('\titeration #%s', attempt); tryReplicate(sourceModel, targetModel, since, options, next); function next(err, conflicts, cps, updates) { var finished = err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS; if (finished) return callback(err, conflicts, cps); run(attempt + 1, cps); } } }
n/a
function resolvePermission(acls, req) { if (!(req instanceof AccessRequest)) { req.registry = this.registry; req = new AccessRequest(req); } // Sort by the matching score in descending order acls = acls.sort(function(rule1, rule2) { return ACL.getMatchingScore(rule2, req) - ACL.getMatchingScore(rule1, req); }); var permission = ACL.DEFAULT; var score = 0; for (var i = 0; i < acls.length; i++) { var candidate = acls[i]; score = ACL.getMatchingScore(candidate, req); if (score < 0) { // the highest scored ACL did not match break; } if (!req.isWildcard()) { // We should stop from the first match for non-wildcard permission = candidate.permission; break; } else { if (req.exactlyMatches(candidate)) { permission = candidate.permission; break; } // For wildcard match, find the strongest permission var candidateOrder = AccessContext.permissionOrder[candidate.permission]; var permissionOrder = AccessContext.permissionOrder[permission]; if (candidateOrder > permissionOrder) { permission = candidate.permission; } } } if (debug.enabled) { debug('The following ACLs were searched: '); acls.forEach(function(acl) { acl.debug(); debug('with score:', acl.score(req)); }); } var res = new AccessRequest({ model: req.model, property: req.property, accessType: req.accessType, permission: permission || ACL.DEFAULT, registry: this.registry}); // Elucidate permission status if DEFAULT res.settleDefaultPermission(); return res; }
...
{inq: [accessType, ACL.ALL, ACL.EXECUTE]};
var req = new AccessRequest({model, property, accessType, registry: this.registry});
var acls = this.getStaticACLs(model, property);
// resolved is an instance of AccessRequest
var resolved = this.resolvePermission(acls, req);
if (resolved && resolved.permission === ACL.DENY) {
debug('Permission denied by statically resolved permission');
debug(' Resolved Permission: %j', resolved);
process.nextTick(function() {
callback(null, resolved);
});
...
resolvePrincipal = function (type, id, cb) { cb = cb || utils.createPromiseCallback(); type = type || ACL.ROLE; this.resolveRelatedModels(); switch (type) { case ACL.ROLE: this.roleModel.findOne({where: {or: [{name: id}, {id: id}]}}, cb); break; case ACL.USER: this.userModel.findOne( {where: {or: [{username: id}, {email: id}, {id: id}]}}, cb); break; case ACL.APP: this.applicationModel.findOne( {where: {or: [{name: id}, {email: id}, {id: id}]}}, cb); break; default: // try resolving a user model with a name matching the principalType var userModel = this.registry.findModel(type); if (userModel) { userModel.findOne( {where: {or: [{username: id}, {email: id}, {id: id}]}}, cb); } else { process.nextTick(function() { var err = new Error(g.f('Invalid principal type: %s', type)); err.statusCode = 400; err.code = 'INVALID_PRINCIPAL_TYPE'; cb(err); }); } } return cb.promise; }
...
* @callback {Function} callback Callback function
* @param {String|Error} err The error object
* @param {Boolean} isMapped is the ACL mapped to the role
*/
ACL.isMappedToRole = function(principalType, principalId, role, cb) {
cb = cb || utils.createPromiseCallback();
var self = this;
this.resolvePrincipal(principalType, principalId,
function(err, principal) {
if (err) return cb(err);
if (principal != null) {
principalId = principal.id;
}
principalType = principalType || 'ROLE';
self.resolvePrincipal('ROLE', role, function(err, role) {
...
resolveRelatedModels = function () { if (!this.roleModel) { var reg = this.registry; this.roleModel = reg.getModelByType('Role'); this.roleMappingModel = reg.getModelByType('RoleMapping'); this.userModel = reg.getModelByType('User'); this.applicationModel = reg.getModelByType('Application'); } }
...
* @callback {Function} callback Callback function
* @param {String|Error} err The error object.
* @param {AccessRequest} result The resolved access request.
*/
ACL.checkAccessForContext = function(context, callback) {
if (!callback) callback = utils.createPromiseCallback();
var self = this;
self.resolveRelatedModels();
var roleModel = self.roleModel;
if (!(context instanceof AccessContext)) {
context.registry = this.registry;
context = new AccessContext(context);
}
...
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
function setupPersistedModel() { // call Model.setup first Model.setup.call(this); var PersistedModel = this; // enable change tracking (usually for replication) if (this.settings.trackChanges) { PersistedModel._defineChangeModel(); PersistedModel.once('dataSourceAttached', function() { PersistedModel.enableChangeTracking(); }); } else if (this.settings.enableRemoteReplication) { PersistedModel._defineChangeModel(); } PersistedModel.setupRemoting(); }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupRemoting = function () { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; // This is just for LB 3.x options.replaceOnPUT = options.replaceOnPUT !== false; function setRemoting(scope, name, options) { var fn = scope[name]; fn._delegate = true; options.isStatic = scope === PersistedModel; PersistedModel.remoteMethod(name, options); } setRemoting(PersistedModel, 'create', { description: 'Create a new instance of the model and persist it into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, allowArray: true, description: 'Model instance data', http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/'}, }); var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], description: 'Patch an existing model instance or insert a new one ' + 'into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'patch', path: '/'}], }; if (!options.replaceOnPUT) { upsertOptions.http.unshift({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); var replaceOrCreateOptions = { description: 'Replace an existing model instance or insert a new one into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'post', path: '/replaceOrCreate'}], }; if (options.replaceOnPUT) { replaceOrCreateOptions.http.push({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'upsertWithWhere', { aliases: ['patchOrCreateWithWhere'], description: 'Update an existing model instance or insert a new one into ' + 'the data source based on the where criteria.', accessType: 'WRITE', accepts: [ {arg: 'where', type: 'object', http: {source: 'query'}, description: 'Criteria to match model instances'}, {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'An object of model property name/value pairs'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/upsertWithWhere'}, }); setRemoting(PersistedModel, 'exists', { description: 'Check whether a model instance exists in the data source.', accessType: 'READ', accepts: [ {arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'exists', type: 'boolean'}, http: [ {verb: 'get', path: '/:id/exists'}, {verb: 'head', path: '/:id'}, ], rest: { // After hook to map exists to 200/404 for HEAD after: function(ctx, cb) { if (ctx.req.method === 'GET') { // For GET, return {exists: true|false} as is return cb(); } if (!ctx.result.exists) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; cb(error); } else { cb(); } ...
...
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
/*!
* Throw an error telling the user that the method is not available and why.
*/
function throwNotAttached(modelName, methodName) {
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.ACL.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.ACL.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
/**
* 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'}, ...}
...
updateLastChange = function (id, data, cb) { var self = this; this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { err = new Error(g.f('No change record found for %s with id %s', self.modelName, id)); err.statusCode = 404; return cb(err); } inst.updateAttributes(data, cb); }); }
...
Conflict.prototype.resolve = function(cb) {
var conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{prev: targetChange.rev},
cb);
});
};
/**
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
debug = function () { if (debug.enabled) { debug('---ACL---'); debug('model %s', this.model); debug('property %s', this.property); debug('principalType %s', this.principalType); debug('principalId %s', this.principalId); debug('accessType %s', this.accessType); debug('permission %s', this.permission); } }
...
}
}
}
if (debug.enabled) {
debug('The following ACLs were searched: ');
acls.forEach(function(acl) {
acl.debug();
debug('with score:', acl.score(req));
});
}
var res = new AccessRequest({
model: req.model,
property: req.property,
accessType: req.accessType,
...
isAllowed = function (defaultPermission) { return this.constructor.isAllowed(this.permission, defaultPermission); }
...
if (debug.enabled) {
debug('---AccessRequest---');
debug(' model %s', this.model);
debug(' property %s', this.property);
debug(' accessType %s', this.accessType);
debug(' permission %s', this.permission);
debug(' isWildcard() %s', this.isWildcard());
debug(' isAllowed() %s', this.isAllowed());
}
};
module.exports.AccessContext = AccessContext;
module.exports.Principal = Principal;
module.exports.AccessRequest = AccessRequest;
...
score = function (req) { return this.constructor.getMatchingScore(this, req); }
...
}
}
if (debug.enabled) {
debug('The following ACLs were searched: ');
acls.forEach(function(acl) {
acl.debug();
debug('with score:', acl.score(req));
});
}
var res = new AccessRequest({
model: req.model,
property: req.property,
accessType: req.accessType,
permission: permission || ACL.DEFAULT,
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_defineChangeModel = function () { var BaseChangeModel = this.registry.getModel('Change'); assert(BaseChangeModel, 'Change model must be defined before enabling change replication'); const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {}; this.Change = BaseChangeModel.extend(this.modelName + '-change', additionalChangeModelProperties, {trackModel: this} ); if (this.dataSource) { attachRelatedModels(this); } // Re-attach related models whenever our datasource is changed. var self = this; this.on('dataSourceAttached', function() { attachRelatedModels(self); }); return this.Change; function attachRelatedModels(self) { self.Change.attachTo(self.dataSource); self.Change.getCheckpointModel().attachTo(self.dataSource); } }
...
// call Model.setup first
Model.setup.call(this);
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
bulkUpdate = function (updates, options, callback) { var tasks = []; var Model = this; var Change = this.getChangeModel(); var conflicts = []; var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof options === 'function') { options = {}; } options = options || {}; buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) { if (err) return callback(err); updates.forEach(function(update) { var id = update.change.modelId; var current = currentMap[id]; switch (update.type) { case Change.UPDATE: tasks.push(function(cb) { applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.CREATE: tasks.push(function(cb) { applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.DELETE: tasks.push(function(cb) { applyDelete(Model, id, current, update.change, conflicts, options, cb); }); break; } }); async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = {conflicts: conflicts}; return callback(err); } callback(); }); }); }
...
function bulkUpdate(_updates, cb) {
debug('\tstarting bulk update');
updates = _updates;
utils.uploadInChunks(
updates,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.bulkUpdate(smallArray, options, function(err) {
// bulk update is a special case where we want to process all chunks and aggregate all errors
chunkCallback(null, err);
});
},
function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) {
...
changes = function (since, filter, callback) { if (typeof since === 'function') { filter = {}; callback = since; since = -1; } if (typeof filter === 'function') { callback = filter; since = -1; filter = {}; } var idName = this.dataSource.idName(this.modelName); var Change = this.getChangeModel(); var model = this; const changeFilter = this.createChangeFilter(since, filter); filter = filter || {}; filter.fields = {}; filter.where = filter.where || {}; filter.fields[idName] = true; // TODO(ritch) this whole thing could be optimized a bit more Change.find(changeFilter, function(err, changes) { if (err) return callback(err); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); var ids = changes.map(function(change) { return change.getModelId(); }); filter.where[idName] = {inq: ids}; model.find(filter, function(err, models) { if (err) return callback(err); var modelIds = models.map(function(m) { return m[idName].toString(); }); callback(null, changes.filter(function(ch) { if (ch.type() === Change.DELETE) return true; return modelIds.indexOf(ch.modelId) > -1; })); }); }); }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
checkpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.bumpLastSeq(cb); }
...
}
cb(err);
});
}
function checkpoints() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) {
if (err) return cb(err);
newSourceCp = source.seq;
targetModel.checkpoint(function(err, target) {
if (err) return cb(err);
newTargetCp = target.seq;
debug('\tcreated checkpoints');
debug('\t\t%s for source model %s', newSourceCp, sourceModel.modelName);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
count = function (where, cb) { throwNotAttached(this.modelName, 'count'); }
n/a
create = function (data, callback) { throwNotAttached(this.modelName, 'create'); }
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
createChangeFilter = function (since, modelFilter) { return { where: { checkpoint: {gte: since}, modelName: this.modelName, }, }; }
...
since = -1;
filter = {};
}
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
var model = this;
const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
...
createChangeStream = function (options, cb) { if (typeof options === 'function') { cb = options; options = undefined; } var idName = this.getIdName(); var Model = this; var changes = new PassThrough({objectMode: true}); var writeable = true; changes.destroy = function() { changes.removeAllListeners('error'); changes.removeAllListeners('end'); writeable = false; changes = null; }; changes.on('error', function() { writeable = false; }); changes.on('end', function() { writeable = false; }); process.nextTick(function() { cb(null, changes); }); Model.observe('after save', createChangeHandler('save')); Model.observe('after delete', createChangeHandler('delete')); function createChangeHandler(type) { return function(ctx, next) { // since it might have set to null via destroy if (!changes) { return next(); } var where = ctx.where; var data = ctx.instance || ctx.data; var whereId = where && where[idName]; // the data includes the id // or the where includes the id var target; if (data && (data[idName] || data[idName] === 0)) { target = data[idName]; } else if (where && (where[idName] || where[idName] === 0)) { target = where[idName]; } var hasTarget = target === 0 || !!target; var change = { target: target, where: where, data: data, }; switch (type) { case 'save': if (ctx.isNewInstance === undefined) { change.type = hasTarget ? 'update' : 'create'; } else { change.type = ctx.isNewInstance ? 'create' : 'update'; } break; case 'delete': change.type = 'remove'; break; } // TODO(ritch) this is ugly... maybe a ReadableStream would be better if (writeable) { changes.write(change); } next(); }; } }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
createUpdates = function (deltas, cb) { var Change = this.getChangeModel(); var updates = []; var Model = this; var tasks = []; deltas.forEach(function(change) { change = new Change(change); var type = change.type(); var update = {type: type, change: change}; switch (type) { case Change.CREATE: case Change.UPDATE: tasks.push(function(cb) { Model.findById(change.modelId, function(err, inst) { if (err) return cb(err); if (!inst) { return cb && cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); } else { update.data = inst; } updates.push(update); cb(); }); }); break; case Change.DELETE: updates.push(update); break; } }); async.parallel(tasks, function(err) { if (err) return cb(err); cb(null, updates); }); }
...
if (diff && diff.deltas && diff.deltas.length) {
debug('\tbuilding a list of updates');
utils.uploadInChunks(
diff.deltas,
replicationChunkSize,
function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback);
},
cb);
} else {
// nothing to replicate
done();
}
}
...
currentCheckpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.current(cb); }
n/a
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
diff = function (since, remoteChanges, callback) { var Change = this.getChangeModel(); Change.diff(this.modelName, since, remoteChanges, callback); }
...
},
});
};
/**
* Get a set of deltas and conflicts since the given checkpoint.
*
* See [Change.diff()](#change-diff) for details.
*
* @param {Number} since Find deltas since this checkpoint.
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
...
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
enableChangeTracking = function () { var Model = this; var Change = this.Change || this._defineChangeModel(); var cleanupInterval = Model.settings.changeCleanupInterval || 30000; assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + ' is not attached to a dataSource'); var idName = this.getIdName(); var idProp = this.definition.properties[idName]; var idType = idProp && idProp.type; var idDefn = idProp && idProp.defaultFn; if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { deprecated('The model ' + this.modelName + ' is tracking changes, ' + 'which requires a string id with GUID/UUID default value.'); } Model.observe('after save', rectifyOnSave); Model.observe('after delete', rectifyOnDelete); // Only run if the run time is server // Can switch off cleanup by setting the interval to -1 if (runtime.isServer && cleanupInterval > 0) { // initial cleanup cleanup(); // cleanup setInterval(cleanup, cleanupInterval); } function cleanup() { Model.rectifyAllChanges(function(err) { if (err) { Model.handleChangeError(err, 'cleanup'); } }); } }
...
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
...
eventNames = function () { [native code] }
n/a
function exists(id, cb) { throwNotAttached(this.modelName, 'exists'); }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
function find(filter, cb) { throwNotAttached(this.modelName, 'find'); }
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, cb) { throwNotAttached(this.modelName, 'findById'); }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.ACL.super_.findById %s', id));
err.statusCode = 404;
...
findLastChange = function (id, cb) { var Change = this.getChangeModel(); Change.findOne({where: {modelId: id}}, cb); }
...
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
...
function findOne(filter, cb) { throwNotAttached(this.modelName, 'findOne'); }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, callback) { throwNotAttached(this.modelName, 'findOrCreate'); }
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getChangeModel = function () { var changeModel = this.Change; var isSetup = changeModel && changeModel.dataSource; assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); return changeModel; }
...
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback);
};
/**
* Get the changes to a model since the specified checkpoint. Provide a filter object
* to reduce the number of results returned.
* @param {Number} since Return only changes since this checkpoint.
...
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getIdName = function () { var Model = this; var ds = Model.getDataSource(); if (ds.idName) { return ds.idName(Model.modelName); } else { return 'id'; } }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
getSourceId = function (cb) { var dataSource = this.dataSource; if (!dataSource) { this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); } assert( dataSource.connector.name, 'Model.getSourceId: cannot get id without dataSource.connector.name' ); var id = [dataSource.connector.name, this.modelName].join('-'); cb(null, id); }
n/a
handleChangeError = function (err, operationName) { if (!err) return; this.emit('error', err, operationName); }
...
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance;
...
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
n/a
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
rectifyAllChanges = function (callback) { this.getChangeModel().rectifyAll(callback); }
...
cleanup();
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
...
rectifyChange = function (id, callback) { var Change = this.getChangeModel(); Change.rectifyModelChanges(this.modelName, [id], callback); }
...
debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),
ctx.Model.modelName, id ? id : 'ALL');
debug('context instance:%j currentInstance:%j where:%j data %j',
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
}
if (id) {
ctx.Model.rectifyChange(id, reportErrorAndNext);
} else {
ctx.Model.rectifyAllChanges(reportErrorAndNext);
}
function reportErrorAndNext(err) {
if (err) {
ctx.Model.handleChangeError(err, 'after save');
...
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
n/a
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
function replaceById(id, data, cb) { throwNotAttached(this.modelName, 'replaceById'); }
n/a
function replaceOrCreate(data, callback) { throwNotAttached(this.modelName, 'replaceOrCreate'); }
n/a
replicate = function (since, targetModel, options, callback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof since === 'function' && since.modelName) { targetModel = since; since = -1; } if (typeof since !== 'object') { since = {source: since, target: since}; } if (typeof options === 'function') { options = {}; } options = options || {}; var sourceModel = this; callback = callback || utils.createPromiseCallback(); debug('replicating %s since %s to %s since %s', sourceModel.modelName, since.source, targetModel.modelName, since.target); if (options.filter) { debug('\twith filter %j', options.filter); } // In order to avoid a race condition between the replication and // other clients modifying the data, we must create the new target // checkpoint as the first step of the replication process. // As a side-effect of that, the replicated changes are associated // with the new target checkpoint. This is actually desired behaviour, // because that way clients replicating *from* the target model // since the new checkpoint will pick these changes up. // However, it increases the likelihood of (false) conflicts being detected. // In order to prevent that, we run the replication multiple times, // until no changes were replicated, but at most MAX_ATTEMPTS times // to prevent starvation. In most cases, the second run will find no changes // to replicate and we are done. var MAX_ATTEMPTS = 3; run(1, since); return callback.promise; function run(attempt, since) { debug('\titeration #%s', attempt); tryReplicate(sourceModel, targetModel, since, options, next); function next(err, conflicts, cps, updates) { var finished = err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS; if (finished) return callback(err, conflicts, cps); run(attempt + 1, cps); } } }
n/a
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
function setupPersistedModel() { // call Model.setup first Model.setup.call(this); var PersistedModel = this; // enable change tracking (usually for replication) if (this.settings.trackChanges) { PersistedModel._defineChangeModel(); PersistedModel.once('dataSourceAttached', function() { PersistedModel.enableChangeTracking(); }); } else if (this.settings.enableRemoteReplication) { PersistedModel._defineChangeModel(); } PersistedModel.setupRemoting(); }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupRemoting = function () { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; // This is just for LB 3.x options.replaceOnPUT = options.replaceOnPUT !== false; function setRemoting(scope, name, options) { var fn = scope[name]; fn._delegate = true; options.isStatic = scope === PersistedModel; PersistedModel.remoteMethod(name, options); } setRemoting(PersistedModel, 'create', { description: 'Create a new instance of the model and persist it into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, allowArray: true, description: 'Model instance data', http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/'}, }); var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], description: 'Patch an existing model instance or insert a new one ' + 'into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'patch', path: '/'}], }; if (!options.replaceOnPUT) { upsertOptions.http.unshift({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); var replaceOrCreateOptions = { description: 'Replace an existing model instance or insert a new one into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'post', path: '/replaceOrCreate'}], }; if (options.replaceOnPUT) { replaceOrCreateOptions.http.push({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'upsertWithWhere', { aliases: ['patchOrCreateWithWhere'], description: 'Update an existing model instance or insert a new one into ' + 'the data source based on the where criteria.', accessType: 'WRITE', accepts: [ {arg: 'where', type: 'object', http: {source: 'query'}, description: 'Criteria to match model instances'}, {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'An object of model property name/value pairs'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/upsertWithWhere'}, }); setRemoting(PersistedModel, 'exists', { description: 'Check whether a model instance exists in the data source.', accessType: 'READ', accepts: [ {arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'exists', type: 'boolean'}, http: [ {verb: 'get', path: '/:id/exists'}, {verb: 'head', path: '/:id'}, ], rest: { // After hook to map exists to 200/404 for HEAD after: function(ctx, cb) { if (ctx.req.method === 'GET') { // For GET, return {exists: true|false} as is return cb(); } if (!ctx.result.exists) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; cb(error); } else { cb(); } ...
...
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
/*!
* Throw an error telling the user that the method is not available and why.
*/
function throwNotAttached(modelName, methodName) {
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.ACL.super_.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.ACL.super_.sharedCtor or {{data}}'))); } }
n/a
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
/**
* 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'}, ...}
...
updateLastChange = function (id, data, cb) { var self = this; this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { err = new Error(g.f('No change record found for %s with id %s', self.modelName, id)); err.statusCode = 404; return cb(err); } inst.updateAttributes(data, cb); }); }
...
Conflict.prototype.resolve = function(cb) {
var conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{prev: targetChange.rev},
cb);
});
};
/**
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
delete = function (cb) { throwNotAttached(this.constructor.modelName, 'destroy'); }
n/a
destroy = function (cb) { throwNotAttached(this.constructor.modelName, 'destroy'); }
...
elapsedSeconds < secondsToLive;
if (isValid) {
process.nextTick(function() {
cb(null, isValid);
});
} else {
this.destroy(function(err) {
cb(err, isValid);
});
}
} catch (e) {
process.nextTick(function() {
cb(e);
});
...
fillCustomChangeProperties = function (change, cb) { // no-op by default cb(); }
...
const model = this.getModelCtor();
const id = this.getModelId();
model.findById(id, function(err, inst) {
if (err) return cb(err);
if (inst) {
inst.fillCustomChangeProperties(change, function() {
const rev = Change.revisionForInst(inst);
prepareAndDoRectify(rev);
});
} else {
prepareAndDoRectify(null);
}
});
...
getId = function () { var data = this.toObject(); if (!data) return; return data[this.getIdName()]; }
...
}
if (!('throws' in options)) {
options.throws = false;
}
var inst = this;
var data = inst.toObject(true);
var id = this.getId();
if (!id) {
return Model.create(this, callback);
}
// validate first
if (!options.validate) {
...
getIdName = function () { return this.constructor.getIdName(); }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
isNewRecord = function () { throwNotAttached(this.constructor.modelName, 'isNewRecord'); }
n/a
function updateAttributes(data, cb) { throwNotAttached(this.modelName, 'updateAttributes'); }
...
try {
User.validatePassword(newPassword);
} catch (err) {
return cb(err);
}
const delta = {password: newPassword};
this.patchAttributes(delta, options, (err, updated) => cb(err));
});
return cb.promise;
};
/**
* Verify a user's identity by sending them a confirmation email.
*
...
function reload(callback) { throwNotAttached(this.constructor.modelName, 'reload'); }
n/a
remove = function (cb) { throwNotAttached(this.constructor.modelName, 'destroy'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
function replaceAttributes(data, cb) { throwNotAttached(this.modelName, 'replaceAttributes'); }
n/a
save = function (options, callback) { var Model = this.constructor; if (typeof options == 'function') { callback = options; options = {}; } callback = callback || function() { }; options = options || {}; if (!('validate' in options)) { options.validate = true; } if (!('throws' in options)) { options.throws = false; } var inst = this; var data = inst.toObject(true); var id = this.getId(); if (!id) { return Model.create(this, callback); } // validate first if (!options.validate) { return save(); } inst.isValid(function(valid) { if (valid) { save(); } else { var err = new Model.ValidationError(inst); // throws option is dangerous for async usage if (options.throws) { throw err; } callback(err, inst); } }); // then save function save() { inst.trigger('save', function(saveDone) { inst.trigger('update', function(updateDone) { Model.upsert(inst, function(err) { inst._initProperties(data); updateDone.call(inst, function() { saveDone.call(inst, function() { callback(err, inst); }); }); }); }, data); }, data); } }
...
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
* Get a change's current revision based on current data.
* @callback {Function} callback
...
setId = function (val) { var ds = this.getDataSource(); this[this.getIdName()] = val; }
...
};
Change.prototype.getModelId = function() {
// TODO(ritch) get rid of the need to create an instance
var Model = this.getModelCtor();
var id = this.modelId;
var m = new Model();
m.setId(id);
return m.getId();
};
Change.prototype.getModel = function(callback) {
var Model = this.constructor.settings.trackModel;
var id = this.getModelId();
Model.findById(id, callback);
...
function updateAttribute(name, value, callback) { throwNotAttached(this.constructor.modelName, 'updateAttribute'); }
n/a
function updateAttributes(data, cb) { throwNotAttached(this.modelName, 'updateAttributes'); }
...
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
inst.updateAttributes(data, cb);
});
};
/**
* Create a change stream. [See here for more info](http://loopback.io/doc/en/lb2/Realtime-server-sent-events.html)
*
* @param {Object} options
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_defineChangeModel = function () { var BaseChangeModel = this.registry.getModel('Change'); assert(BaseChangeModel, 'Change model must be defined before enabling change replication'); const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {}; this.Change = BaseChangeModel.extend(this.modelName + '-change', additionalChangeModelProperties, {trackModel: this} ); if (this.dataSource) { attachRelatedModels(this); } // Re-attach related models whenever our datasource is changed. var self = this; this.on('dataSourceAttached', function() { attachRelatedModels(self); }); return this.Change; function attachRelatedModels(self) { self.Change.attachTo(self.dataSource); self.Change.getCheckpointModel().attachTo(self.dataSource); } }
...
// call Model.setup first
Model.setup.call(this);
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
bulkUpdate = function (updates, options, callback) { var tasks = []; var Model = this; var Change = this.getChangeModel(); var conflicts = []; var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof options === 'function') { options = {}; } options = options || {}; buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) { if (err) return callback(err); updates.forEach(function(update) { var id = update.change.modelId; var current = currentMap[id]; switch (update.type) { case Change.UPDATE: tasks.push(function(cb) { applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.CREATE: tasks.push(function(cb) { applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.DELETE: tasks.push(function(cb) { applyDelete(Model, id, current, update.change, conflicts, options, cb); }); break; } }); async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = {conflicts: conflicts}; return callback(err); } callback(); }); }); }
...
function bulkUpdate(_updates, cb) {
debug('\tstarting bulk update');
updates = _updates;
utils.uploadInChunks(
updates,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.bulkUpdate(smallArray, options, function(err) {
// bulk update is a special case where we want to process all chunks and aggregate all errors
chunkCallback(null, err);
});
},
function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) {
...
changes = function (since, filter, callback) { if (typeof since === 'function') { filter = {}; callback = since; since = -1; } if (typeof filter === 'function') { callback = filter; since = -1; filter = {}; } var idName = this.dataSource.idName(this.modelName); var Change = this.getChangeModel(); var model = this; const changeFilter = this.createChangeFilter(since, filter); filter = filter || {}; filter.fields = {}; filter.where = filter.where || {}; filter.fields[idName] = true; // TODO(ritch) this whole thing could be optimized a bit more Change.find(changeFilter, function(err, changes) { if (err) return callback(err); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); var ids = changes.map(function(change) { return change.getModelId(); }); filter.where[idName] = {inq: ids}; model.find(filter, function(err, models) { if (err) return callback(err); var modelIds = models.map(function(m) { return m[idName].toString(); }); callback(null, changes.filter(function(ch) { if (ch.type() === Change.DELETE) return true; return modelIds.indexOf(ch.modelId) > -1; })); }); }); }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
checkpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.bumpLastSeq(cb); }
...
}
cb(err);
});
}
function checkpoints() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) {
if (err) return cb(err);
newSourceCp = source.seq;
targetModel.checkpoint(function(err, target) {
if (err) return cb(err);
newTargetCp = target.seq;
debug('\tcreated checkpoints');
debug('\t\t%s for source model %s', newSourceCp, sourceModel.modelName);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
count = function (where, cb) { throwNotAttached(this.modelName, 'count'); }
n/a
create = function (data, callback) { throwNotAttached(this.modelName, 'create'); }
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
createAccessTokenId = function (fn) { uid(this.settings.accessTokenIdLength || DEFAULT_TOKEN_LEN, function(err, guid) { if (err) { fn(err); } else { fn(null, guid); } }); }
...
*/
AccessToken.observe('before save', function(ctx, next) {
if (!ctx.instance || ctx.instance.id) {
// We are running a partial update or the instance already has an id
return next();
}
AccessToken.createAccessTokenId(function(err, id) {
if (err) return next(err);
ctx.instance.id = id;
next();
});
});
/**
...
createChangeFilter = function (since, modelFilter) { return { where: { checkpoint: {gte: since}, modelName: this.modelName, }, }; }
...
since = -1;
filter = {};
}
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
var model = this;
const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
...
createChangeStream = function (options, cb) { if (typeof options === 'function') { cb = options; options = undefined; } var idName = this.getIdName(); var Model = this; var changes = new PassThrough({objectMode: true}); var writeable = true; changes.destroy = function() { changes.removeAllListeners('error'); changes.removeAllListeners('end'); writeable = false; changes = null; }; changes.on('error', function() { writeable = false; }); changes.on('end', function() { writeable = false; }); process.nextTick(function() { cb(null, changes); }); Model.observe('after save', createChangeHandler('save')); Model.observe('after delete', createChangeHandler('delete')); function createChangeHandler(type) { return function(ctx, next) { // since it might have set to null via destroy if (!changes) { return next(); } var where = ctx.where; var data = ctx.instance || ctx.data; var whereId = where && where[idName]; // the data includes the id // or the where includes the id var target; if (data && (data[idName] || data[idName] === 0)) { target = data[idName]; } else if (where && (where[idName] || where[idName] === 0)) { target = where[idName]; } var hasTarget = target === 0 || !!target; var change = { target: target, where: where, data: data, }; switch (type) { case 'save': if (ctx.isNewInstance === undefined) { change.type = hasTarget ? 'update' : 'create'; } else { change.type = ctx.isNewInstance ? 'create' : 'update'; } break; case 'delete': change.type = 'remove'; break; } // TODO(ritch) this is ugly... maybe a ReadableStream would be better if (writeable) { changes.write(change); } next(); }; } }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
createUpdates = function (deltas, cb) { var Change = this.getChangeModel(); var updates = []; var Model = this; var tasks = []; deltas.forEach(function(change) { change = new Change(change); var type = change.type(); var update = {type: type, change: change}; switch (type) { case Change.CREATE: case Change.UPDATE: tasks.push(function(cb) { Model.findById(change.modelId, function(err, inst) { if (err) return cb(err); if (!inst) { return cb && cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); } else { update.data = inst; } updates.push(update); cb(); }); }); break; case Change.DELETE: updates.push(update); break; } }); async.parallel(tasks, function(err) { if (err) return cb(err); cb(null, updates); }); }
...
if (diff && diff.deltas && diff.deltas.length) {
debug('\tbuilding a list of updates');
utils.uploadInChunks(
diff.deltas,
replicationChunkSize,
function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback);
},
cb);
} else {
// nothing to replicate
done();
}
}
...
currentCheckpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.current(cb); }
n/a
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
diff = function (since, remoteChanges, callback) { var Change = this.getChangeModel(); Change.diff(this.modelName, since, remoteChanges, callback); }
...
},
});
};
/**
* Get a set of deltas and conflicts since the given checkpoint.
*
* See [Change.diff()](#change-diff) for details.
*
* @param {Number} since Find deltas since this checkpoint.
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
...
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
enableChangeTracking = function () { var Model = this; var Change = this.Change || this._defineChangeModel(); var cleanupInterval = Model.settings.changeCleanupInterval || 30000; assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + ' is not attached to a dataSource'); var idName = this.getIdName(); var idProp = this.definition.properties[idName]; var idType = idProp && idProp.type; var idDefn = idProp && idProp.defaultFn; if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { deprecated('The model ' + this.modelName + ' is tracking changes, ' + 'which requires a string id with GUID/UUID default value.'); } Model.observe('after save', rectifyOnSave); Model.observe('after delete', rectifyOnDelete); // Only run if the run time is server // Can switch off cleanup by setting the interval to -1 if (runtime.isServer && cleanupInterval > 0) { // initial cleanup cleanup(); // cleanup setInterval(cleanup, cleanupInterval); } function cleanup() { Model.rectifyAllChanges(function(err) { if (err) { Model.handleChangeError(err, 'cleanup'); } }); } }
...
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
...
eventNames = function () { [native code] }
n/a
function exists(id, cb) { throwNotAttached(this.modelName, 'exists'); }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
function find(filter, cb) { throwNotAttached(this.modelName, 'find'); }
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, cb) { throwNotAttached(this.modelName, 'findById'); }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.AccessToken.findById %s', id));
err.statusCode = 404;
...
findForRequest = function (req, options, cb) { if (cb === undefined && typeof options === 'function') { cb = options; options = {}; } var id = tokenIdForRequest(req, options); if (id) { this.findById(id, function(err, token) { if (err) { cb(err); } else if (token) { token.validate(function(err, isValid) { if (err) { cb(err); } else if (isValid) { cb(null, token); } else { var e = new Error(g.f('Invalid Access Token')); e.status = e.statusCode = 401; e.code = 'INVALID_TOKEN'; cb(e); } }); } else { cb(); } }); } else { process.nextTick(function() { cb(); }); } }
...
// req.accessToken.id is defined, which means that some other middleware has identified a valid user.
// when overwriteExistingToken is not set to a truthy value, skip searching for credentials.
rewriteUserLiteral(req, currentUserLiteral);
return next();
}
// continue normal operation (as if req.accessToken was undefined)
}
TokenModel.findForRequest(req, options, function(err, token) {
req.accessToken = token || null;
rewriteUserLiteral(req, currentUserLiteral);
var ctx = req.loopbackContext;
if (ctx && ctx.active) ctx.set('accessToken', token);
next(err);
});
};
...
findLastChange = function (id, cb) { var Change = this.getChangeModel(); Change.findOne({where: {modelId: id}}, cb); }
...
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
...
function findOne(filter, cb) { throwNotAttached(this.modelName, 'findOne'); }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, callback) { throwNotAttached(this.modelName, 'findOrCreate'); }
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getChangeModel = function () { var changeModel = this.Change; var isSetup = changeModel && changeModel.dataSource; assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); return changeModel; }
...
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback);
};
/**
* Get the changes to a model since the specified checkpoint. Provide a filter object
* to reduce the number of results returned.
* @param {Number} since Return only changes since this checkpoint.
...
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getIdName = function () { var Model = this; var ds = Model.getDataSource(); if (ds.idName) { return ds.idName(Model.modelName); } else { return 'id'; } }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
getSourceId = function (cb) { var dataSource = this.dataSource; if (!dataSource) { this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); } assert( dataSource.connector.name, 'Model.getSourceId: cannot get id without dataSource.connector.name' ); var id = [dataSource.connector.name, this.modelName].join('-'); cb(null, id); }
n/a
handleChangeError = function (err, operationName) { if (!err) return; this.emit('error', err, operationName); }
...
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance;
...
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
n/a
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
rectifyAllChanges = function (callback) { this.getChangeModel().rectifyAll(callback); }
...
cleanup();
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
...
rectifyChange = function (id, callback) { var Change = this.getChangeModel(); Change.rectifyModelChanges(this.modelName, [id], callback); }
...
debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),
ctx.Model.modelName, id ? id : 'ALL');
debug('context instance:%j currentInstance:%j where:%j data %j',
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
}
if (id) {
ctx.Model.rectifyChange(id, reportErrorAndNext);
} else {
ctx.Model.rectifyAllChanges(reportErrorAndNext);
}
function reportErrorAndNext(err) {
if (err) {
ctx.Model.handleChangeError(err, 'after save');
...
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
n/a
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
function replaceById(id, data, cb) { throwNotAttached(this.modelName, 'replaceById'); }
n/a
function replaceOrCreate(data, callback) { throwNotAttached(this.modelName, 'replaceOrCreate'); }
n/a
replicate = function (since, targetModel, options, callback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof since === 'function' && since.modelName) { targetModel = since; since = -1; } if (typeof since !== 'object') { since = {source: since, target: since}; } if (typeof options === 'function') { options = {}; } options = options || {}; var sourceModel = this; callback = callback || utils.createPromiseCallback(); debug('replicating %s since %s to %s since %s', sourceModel.modelName, since.source, targetModel.modelName, since.target); if (options.filter) { debug('\twith filter %j', options.filter); } // In order to avoid a race condition between the replication and // other clients modifying the data, we must create the new target // checkpoint as the first step of the replication process. // As a side-effect of that, the replicated changes are associated // with the new target checkpoint. This is actually desired behaviour, // because that way clients replicating *from* the target model // since the new checkpoint will pick these changes up. // However, it increases the likelihood of (false) conflicts being detected. // In order to prevent that, we run the replication multiple times, // until no changes were replicated, but at most MAX_ATTEMPTS times // to prevent starvation. In most cases, the second run will find no changes // to replicate and we are done. var MAX_ATTEMPTS = 3; run(1, since); return callback.promise; function run(attempt, since) { debug('\titeration #%s', attempt); tryReplicate(sourceModel, targetModel, since, options, next); function next(err, conflicts, cps, updates) { var finished = err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS; if (finished) return callback(err, conflicts, cps); run(attempt + 1, cps); } } }
n/a
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
function setupPersistedModel() { // call Model.setup first Model.setup.call(this); var PersistedModel = this; // enable change tracking (usually for replication) if (this.settings.trackChanges) { PersistedModel._defineChangeModel(); PersistedModel.once('dataSourceAttached', function() { PersistedModel.enableChangeTracking(); }); } else if (this.settings.enableRemoteReplication) { PersistedModel._defineChangeModel(); } PersistedModel.setupRemoting(); }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupRemoting = function () { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; // This is just for LB 3.x options.replaceOnPUT = options.replaceOnPUT !== false; function setRemoting(scope, name, options) { var fn = scope[name]; fn._delegate = true; options.isStatic = scope === PersistedModel; PersistedModel.remoteMethod(name, options); } setRemoting(PersistedModel, 'create', { description: 'Create a new instance of the model and persist it into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, allowArray: true, description: 'Model instance data', http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/'}, }); var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], description: 'Patch an existing model instance or insert a new one ' + 'into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'patch', path: '/'}], }; if (!options.replaceOnPUT) { upsertOptions.http.unshift({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); var replaceOrCreateOptions = { description: 'Replace an existing model instance or insert a new one into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'post', path: '/replaceOrCreate'}], }; if (options.replaceOnPUT) { replaceOrCreateOptions.http.push({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'upsertWithWhere', { aliases: ['patchOrCreateWithWhere'], description: 'Update an existing model instance or insert a new one into ' + 'the data source based on the where criteria.', accessType: 'WRITE', accepts: [ {arg: 'where', type: 'object', http: {source: 'query'}, description: 'Criteria to match model instances'}, {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'An object of model property name/value pairs'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/upsertWithWhere'}, }); setRemoting(PersistedModel, 'exists', { description: 'Check whether a model instance exists in the data source.', accessType: 'READ', accepts: [ {arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'exists', type: 'boolean'}, http: [ {verb: 'get', path: '/:id/exists'}, {verb: 'head', path: '/:id'}, ], rest: { // After hook to map exists to 200/404 for HEAD after: function(ctx, cb) { if (ctx.req.method === 'GET') { // For GET, return {exists: true|false} as is return cb(); } if (!ctx.result.exists) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; cb(error); } else { cb(); } ...
...
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
/*!
* Throw an error telling the user that the method is not available and why.
*/
function throwNotAttached(modelName, methodName) {
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.AccessToken.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.AccessToken.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
/**
* 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'}, ...}
...
updateLastChange = function (id, data, cb) { var self = this; this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { err = new Error(g.f('No change record found for %s with id %s', self.modelName, id)); err.statusCode = 404; return cb(err); } inst.updateAttributes(data, cb); }); }
...
Conflict.prototype.resolve = function(cb) {
var conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{prev: targetChange.rev},
cb);
});
};
/**
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
validate = function (cb) { try { assert( this.created && typeof this.created.getTime === 'function', 'token.created must be a valid Date' ); assert(this.ttl !== 0, 'token.ttl must be not be 0'); assert(this.ttl, 'token.ttl must exist'); assert(this.ttl >= -1, 'token.ttl must be >= -1'); var AccessToken = this.constructor; var userRelation = AccessToken.relations.user; // may not be set up var User = userRelation && userRelation.modelTo; // redefine user model if accessToken's principalType is available if (this.principalType) { User = AccessToken.registry.findModel(this.principalType); if (!User) { process.nextTick(function() { return cb(null, false); }); } } var now = Date.now(); var created = this.created.getTime(); var elapsedSeconds = (now - created) / 1000; var secondsToLive = this.ttl; var eternalTokensAllowed = !!(User && User.settings.allowEternalTokens); var isEternalToken = secondsToLive === -1; var isValid = isEternalToken ? eternalTokensAllowed : elapsedSeconds < secondsToLive; if (isValid) { process.nextTick(function() { cb(null, isValid); }); } else { this.destroy(function(err) { cb(err, isValid); }); } } catch (e) { process.nextTick(function() { cb(e); }); } }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_defineChangeModel = function () { var BaseChangeModel = this.registry.getModel('Change'); assert(BaseChangeModel, 'Change model must be defined before enabling change replication'); const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {}; this.Change = BaseChangeModel.extend(this.modelName + '-change', additionalChangeModelProperties, {trackModel: this} ); if (this.dataSource) { attachRelatedModels(this); } // Re-attach related models whenever our datasource is changed. var self = this; this.on('dataSourceAttached', function() { attachRelatedModels(self); }); return this.Change; function attachRelatedModels(self) { self.Change.attachTo(self.dataSource); self.Change.getCheckpointModel().attachTo(self.dataSource); } }
...
// call Model.setup first
Model.setup.call(this);
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
authenticate = function (appId, key, cb) { cb = cb || utils.createPromiseCallback(); this.findById(appId, function(err, app) { if (err || !app) { cb(err, null); return cb.promise; } var result = null; var keyNames = ['clientKey', 'javaScriptKey', 'restApiKey', 'windowsKey', 'masterKey']; for (var i = 0; i < keyNames.length; i++) { if (app[keyNames[i]] === key) { result = { application: app, keyType: keyNames[i], }; break; } } cb(null, result); }); return cb.promise; }
n/a
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
bulkUpdate = function (updates, options, callback) { var tasks = []; var Model = this; var Change = this.getChangeModel(); var conflicts = []; var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof options === 'function') { options = {}; } options = options || {}; buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) { if (err) return callback(err); updates.forEach(function(update) { var id = update.change.modelId; var current = currentMap[id]; switch (update.type) { case Change.UPDATE: tasks.push(function(cb) { applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.CREATE: tasks.push(function(cb) { applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.DELETE: tasks.push(function(cb) { applyDelete(Model, id, current, update.change, conflicts, options, cb); }); break; } }); async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = {conflicts: conflicts}; return callback(err); } callback(); }); }); }
...
function bulkUpdate(_updates, cb) {
debug('\tstarting bulk update');
updates = _updates;
utils.uploadInChunks(
updates,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.bulkUpdate(smallArray, options, function(err) {
// bulk update is a special case where we want to process all chunks and aggregate all errors
chunkCallback(null, err);
});
},
function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) {
...
changes = function (since, filter, callback) { if (typeof since === 'function') { filter = {}; callback = since; since = -1; } if (typeof filter === 'function') { callback = filter; since = -1; filter = {}; } var idName = this.dataSource.idName(this.modelName); var Change = this.getChangeModel(); var model = this; const changeFilter = this.createChangeFilter(since, filter); filter = filter || {}; filter.fields = {}; filter.where = filter.where || {}; filter.fields[idName] = true; // TODO(ritch) this whole thing could be optimized a bit more Change.find(changeFilter, function(err, changes) { if (err) return callback(err); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); var ids = changes.map(function(change) { return change.getModelId(); }); filter.where[idName] = {inq: ids}; model.find(filter, function(err, models) { if (err) return callback(err); var modelIds = models.map(function(m) { return m[idName].toString(); }); callback(null, changes.filter(function(ch) { if (ch.type() === Change.DELETE) return true; return modelIds.indexOf(ch.modelId) > -1; })); }); }); }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
checkpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.bumpLastSeq(cb); }
...
}
cb(err);
});
}
function checkpoints() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) {
if (err) return cb(err);
newSourceCp = source.seq;
targetModel.checkpoint(function(err, target) {
if (err) return cb(err);
newTargetCp = target.seq;
debug('\tcreated checkpoints');
debug('\t\t%s for source model %s', newSourceCp, sourceModel.modelName);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
count = function (where, cb) { throwNotAttached(this.modelName, 'count'); }
n/a
create = function (data, callback) { throwNotAttached(this.modelName, 'create'); }
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
createChangeFilter = function (since, modelFilter) { return { where: { checkpoint: {gte: since}, modelName: this.modelName, }, }; }
...
since = -1;
filter = {};
}
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
var model = this;
const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
...
createChangeStream = function (options, cb) { if (typeof options === 'function') { cb = options; options = undefined; } var idName = this.getIdName(); var Model = this; var changes = new PassThrough({objectMode: true}); var writeable = true; changes.destroy = function() { changes.removeAllListeners('error'); changes.removeAllListeners('end'); writeable = false; changes = null; }; changes.on('error', function() { writeable = false; }); changes.on('end', function() { writeable = false; }); process.nextTick(function() { cb(null, changes); }); Model.observe('after save', createChangeHandler('save')); Model.observe('after delete', createChangeHandler('delete')); function createChangeHandler(type) { return function(ctx, next) { // since it might have set to null via destroy if (!changes) { return next(); } var where = ctx.where; var data = ctx.instance || ctx.data; var whereId = where && where[idName]; // the data includes the id // or the where includes the id var target; if (data && (data[idName] || data[idName] === 0)) { target = data[idName]; } else if (where && (where[idName] || where[idName] === 0)) { target = where[idName]; } var hasTarget = target === 0 || !!target; var change = { target: target, where: where, data: data, }; switch (type) { case 'save': if (ctx.isNewInstance === undefined) { change.type = hasTarget ? 'update' : 'create'; } else { change.type = ctx.isNewInstance ? 'create' : 'update'; } break; case 'delete': change.type = 'remove'; break; } // TODO(ritch) this is ugly... maybe a ReadableStream would be better if (writeable) { changes.write(change); } next(); }; } }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
createUpdates = function (deltas, cb) { var Change = this.getChangeModel(); var updates = []; var Model = this; var tasks = []; deltas.forEach(function(change) { change = new Change(change); var type = change.type(); var update = {type: type, change: change}; switch (type) { case Change.CREATE: case Change.UPDATE: tasks.push(function(cb) { Model.findById(change.modelId, function(err, inst) { if (err) return cb(err); if (!inst) { return cb && cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); } else { update.data = inst; } updates.push(update); cb(); }); }); break; case Change.DELETE: updates.push(update); break; } }); async.parallel(tasks, function(err) { if (err) return cb(err); cb(null, updates); }); }
...
if (diff && diff.deltas && diff.deltas.length) {
debug('\tbuilding a list of updates');
utils.uploadInChunks(
diff.deltas,
replicationChunkSize,
function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback);
},
cb);
} else {
// nothing to replicate
done();
}
}
...
currentCheckpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.current(cb); }
n/a
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
diff = function (since, remoteChanges, callback) { var Change = this.getChangeModel(); Change.diff(this.modelName, since, remoteChanges, callback); }
...
},
});
};
/**
* Get a set of deltas and conflicts since the given checkpoint.
*
* See [Change.diff()](#change-diff) for details.
*
* @param {Number} since Find deltas since this checkpoint.
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
...
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
enableChangeTracking = function () { var Model = this; var Change = this.Change || this._defineChangeModel(); var cleanupInterval = Model.settings.changeCleanupInterval || 30000; assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + ' is not attached to a dataSource'); var idName = this.getIdName(); var idProp = this.definition.properties[idName]; var idType = idProp && idProp.type; var idDefn = idProp && idProp.defaultFn; if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { deprecated('The model ' + this.modelName + ' is tracking changes, ' + 'which requires a string id with GUID/UUID default value.'); } Model.observe('after save', rectifyOnSave); Model.observe('after delete', rectifyOnDelete); // Only run if the run time is server // Can switch off cleanup by setting the interval to -1 if (runtime.isServer && cleanupInterval > 0) { // initial cleanup cleanup(); // cleanup setInterval(cleanup, cleanupInterval); } function cleanup() { Model.rectifyAllChanges(function(err) { if (err) { Model.handleChangeError(err, 'cleanup'); } }); } }
...
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
...
eventNames = function () { [native code] }
n/a
function exists(id, cb) { throwNotAttached(this.modelName, 'exists'); }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
function find(filter, cb) { throwNotAttached(this.modelName, 'find'); }
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, cb) { throwNotAttached(this.modelName, 'findById'); }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.Application.findById %s', id));
err.statusCode = 404;
...
findLastChange = function (id, cb) { var Change = this.getChangeModel(); Change.findOne({where: {modelId: id}}, cb); }
...
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
...
function findOne(filter, cb) { throwNotAttached(this.modelName, 'findOne'); }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, callback) { throwNotAttached(this.modelName, 'findOrCreate'); }
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getChangeModel = function () { var changeModel = this.Change; var isSetup = changeModel && changeModel.dataSource; assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); return changeModel; }
...
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback);
};
/**
* Get the changes to a model since the specified checkpoint. Provide a filter object
* to reduce the number of results returned.
* @param {Number} since Return only changes since this checkpoint.
...
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getIdName = function () { var Model = this; var ds = Model.getDataSource(); if (ds.idName) { return ds.idName(Model.modelName); } else { return 'id'; } }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
getSourceId = function (cb) { var dataSource = this.dataSource; if (!dataSource) { this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); } assert( dataSource.connector.name, 'Model.getSourceId: cannot get id without dataSource.connector.name' ); var id = [dataSource.connector.name, this.modelName].join('-'); cb(null, id); }
n/a
handleChangeError = function (err, operationName) { if (!err) return; this.emit('error', err, operationName); }
...
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance;
...
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
n/a
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
rectifyAllChanges = function (callback) { this.getChangeModel().rectifyAll(callback); }
...
cleanup();
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
...
rectifyChange = function (id, callback) { var Change = this.getChangeModel(); Change.rectifyModelChanges(this.modelName, [id], callback); }
...
debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),
ctx.Model.modelName, id ? id : 'ALL');
debug('context instance:%j currentInstance:%j where:%j data %j',
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
}
if (id) {
ctx.Model.rectifyChange(id, reportErrorAndNext);
} else {
ctx.Model.rectifyAllChanges(reportErrorAndNext);
}
function reportErrorAndNext(err) {
if (err) {
ctx.Model.handleChangeError(err, 'after save');
...
register = function (owner, name, options, cb) { assert(owner, 'owner is required'); assert(name, 'name is required'); if (typeof options === 'function' && !cb) { cb = options; options = {}; } cb = cb || utils.createPromiseCallback(); var props = {owner: owner, name: name}; for (var p in options) { if (!(p in props)) { props[p] = options[p]; } } this.create(props, cb); return cb.promise; }
n/a
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
n/a
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
function replaceById(id, data, cb) { throwNotAttached(this.modelName, 'replaceById'); }
n/a
function replaceOrCreate(data, callback) { throwNotAttached(this.modelName, 'replaceOrCreate'); }
n/a
replicate = function (since, targetModel, options, callback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof since === 'function' && since.modelName) { targetModel = since; since = -1; } if (typeof since !== 'object') { since = {source: since, target: since}; } if (typeof options === 'function') { options = {}; } options = options || {}; var sourceModel = this; callback = callback || utils.createPromiseCallback(); debug('replicating %s since %s to %s since %s', sourceModel.modelName, since.source, targetModel.modelName, since.target); if (options.filter) { debug('\twith filter %j', options.filter); } // In order to avoid a race condition between the replication and // other clients modifying the data, we must create the new target // checkpoint as the first step of the replication process. // As a side-effect of that, the replicated changes are associated // with the new target checkpoint. This is actually desired behaviour, // because that way clients replicating *from* the target model // since the new checkpoint will pick these changes up. // However, it increases the likelihood of (false) conflicts being detected. // In order to prevent that, we run the replication multiple times, // until no changes were replicated, but at most MAX_ATTEMPTS times // to prevent starvation. In most cases, the second run will find no changes // to replicate and we are done. var MAX_ATTEMPTS = 3; run(1, since); return callback.promise; function run(attempt, since) { debug('\titeration #%s', attempt); tryReplicate(sourceModel, targetModel, since, options, next); function next(err, conflicts, cps, updates) { var finished = err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS; if (finished) return callback(err, conflicts, cps); run(attempt + 1, cps); } } }
n/a
resetKeys = function (appId, cb) { cb = cb || utils.createPromiseCallback(); this.findById(appId, function(err, app) { if (err) { if (cb) cb(err, app); return; } app.resetKeys(cb); }); return cb.promise; }
n/a
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
function setupPersistedModel() { // call Model.setup first Model.setup.call(this); var PersistedModel = this; // enable change tracking (usually for replication) if (this.settings.trackChanges) { PersistedModel._defineChangeModel(); PersistedModel.once('dataSourceAttached', function() { PersistedModel.enableChangeTracking(); }); } else if (this.settings.enableRemoteReplication) { PersistedModel._defineChangeModel(); } PersistedModel.setupRemoting(); }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupRemoting = function () { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; // This is just for LB 3.x options.replaceOnPUT = options.replaceOnPUT !== false; function setRemoting(scope, name, options) { var fn = scope[name]; fn._delegate = true; options.isStatic = scope === PersistedModel; PersistedModel.remoteMethod(name, options); } setRemoting(PersistedModel, 'create', { description: 'Create a new instance of the model and persist it into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, allowArray: true, description: 'Model instance data', http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/'}, }); var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], description: 'Patch an existing model instance or insert a new one ' + 'into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'patch', path: '/'}], }; if (!options.replaceOnPUT) { upsertOptions.http.unshift({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); var replaceOrCreateOptions = { description: 'Replace an existing model instance or insert a new one into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'post', path: '/replaceOrCreate'}], }; if (options.replaceOnPUT) { replaceOrCreateOptions.http.push({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'upsertWithWhere', { aliases: ['patchOrCreateWithWhere'], description: 'Update an existing model instance or insert a new one into ' + 'the data source based on the where criteria.', accessType: 'WRITE', accepts: [ {arg: 'where', type: 'object', http: {source: 'query'}, description: 'Criteria to match model instances'}, {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'An object of model property name/value pairs'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/upsertWithWhere'}, }); setRemoting(PersistedModel, 'exists', { description: 'Check whether a model instance exists in the data source.', accessType: 'READ', accepts: [ {arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'exists', type: 'boolean'}, http: [ {verb: 'get', path: '/:id/exists'}, {verb: 'head', path: '/:id'}, ], rest: { // After hook to map exists to 200/404 for HEAD after: function(ctx, cb) { if (ctx.req.method === 'GET') { // For GET, return {exists: true|false} as is return cb(); } if (!ctx.result.exists) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; cb(error); } else { cb(); } ...
...
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
/*!
* Throw an error telling the user that the method is not available and why.
*/
function throwNotAttached(modelName, methodName) {
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.Application.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.Application.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
/**
* 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'}, ...}
...
updateLastChange = function (id, data, cb) { var self = this; this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { err = new Error(g.f('No change record found for %s with id %s', self.modelName, id)); err.statusCode = 404; return cb(err); } inst.updateAttributes(data, cb); }); }
...
Conflict.prototype.resolve = function(cb) {
var conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{prev: targetChange.rev},
cb);
});
};
/**
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
resetKeys = function (cb) { this.clientKey = generateKey('client'); this.javaScriptKey = generateKey('javaScript'); this.restApiKey = generateKey('restApi'); this.windowsKey = generateKey('windows'); this.masterKey = generateKey('master'); this.modified = new Date(); this.save(cb); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function Conflict(modelId, SourceModel, TargetModel) { this.SourceModel = SourceModel; this.TargetModel = TargetModel; this.SourceChange = SourceModel.getChangeModel(); this.TargetChange = TargetModel.getChangeModel(); this.modelId = modelId; }
...
debug('\treplication finished');
debug('\t\t%s conflict(s) detected', diff.conflicts.length);
debug('\t\t%s change(s) applied', updates ? updates.length : 0);
debug('\t\tnew checkpoints: { source: %j, target: %j }',
newSourceCp, newTargetCp);
var conflicts = diff.conflicts.map(function(change) {
return new Change.Conflict(
change.modelId, sourceModel, targetModel
);
});
if (conflicts.length) {
sourceModel.emit('conflicts', conflicts);
}
...
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_defineChangeModel = function () { var BaseChangeModel = this.registry.getModel('Change'); assert(BaseChangeModel, 'Change model must be defined before enabling change replication'); const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {}; this.Change = BaseChangeModel.extend(this.modelName + '-change', additionalChangeModelProperties, {trackModel: this} ); if (this.dataSource) { attachRelatedModels(this); } // Re-attach related models whenever our datasource is changed. var self = this; this.on('dataSourceAttached', function() { attachRelatedModels(self); }); return this.Change; function attachRelatedModels(self) { self.Change.attachTo(self.dataSource); self.Change.getCheckpointModel().attachTo(self.dataSource); } }
...
// call Model.setup first
Model.setup.call(this);
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
bothDeleted = function (a, b) { return a.type() === Change.DELETE && b.type() === Change.DELETE; }
...
* @param {Change} change
* @return {Boolean}
*/
Change.prototype.conflictsWith = function(change) {
if (!change) return false;
if (this.equals(change)) return false;
if (Change.bothDeleted(this, change)) return false;
if (this.isBasedOn(change)) return false;
return true;
};
/**
* Are both changes deletes?
* @param {Change} a
...
bulkUpdate = function (updates, options, callback) { var tasks = []; var Model = this; var Change = this.getChangeModel(); var conflicts = []; var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof options === 'function') { options = {}; } options = options || {}; buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) { if (err) return callback(err); updates.forEach(function(update) { var id = update.change.modelId; var current = currentMap[id]; switch (update.type) { case Change.UPDATE: tasks.push(function(cb) { applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.CREATE: tasks.push(function(cb) { applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.DELETE: tasks.push(function(cb) { applyDelete(Model, id, current, update.change, conflicts, options, cb); }); break; } }); async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = {conflicts: conflicts}; return callback(err); } callback(); }); }); }
...
function bulkUpdate(_updates, cb) {
debug('\tstarting bulk update');
updates = _updates;
utils.uploadInChunks(
updates,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.bulkUpdate(smallArray, options, function(err) {
// bulk update is a special case where we want to process all chunks and aggregate all errors
chunkCallback(null, err);
});
},
function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) {
...
changes = function (since, filter, callback) { if (typeof since === 'function') { filter = {}; callback = since; since = -1; } if (typeof filter === 'function') { callback = filter; since = -1; filter = {}; } var idName = this.dataSource.idName(this.modelName); var Change = this.getChangeModel(); var model = this; const changeFilter = this.createChangeFilter(since, filter); filter = filter || {}; filter.fields = {}; filter.where = filter.where || {}; filter.fields[idName] = true; // TODO(ritch) this whole thing could be optimized a bit more Change.find(changeFilter, function(err, changes) { if (err) return callback(err); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); var ids = changes.map(function(change) { return change.getModelId(); }); filter.where[idName] = {inq: ids}; model.find(filter, function(err, models) { if (err) return callback(err); var modelIds = models.map(function(m) { return m[idName].toString(); }); callback(null, changes.filter(function(ch) { if (ch.type() === Change.DELETE) return true; return modelIds.indexOf(ch.modelId) > -1; })); }); }); }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
checkpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.bumpLastSeq(cb); }
...
}
cb(err);
});
}
function checkpoints() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) {
if (err) return cb(err);
newSourceCp = source.seq;
targetModel.checkpoint(function(err, target) {
if (err) return cb(err);
newTargetCp = target.seq;
debug('\tcreated checkpoints');
debug('\t\t%s for source model %s', newSourceCp, sourceModel.modelName);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
count = function (where, cb) { throwNotAttached(this.modelName, 'count'); }
n/a
create = function (data, callback) { throwNotAttached(this.modelName, 'create'); }
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
createChangeFilter = function (since, modelFilter) { return { where: { checkpoint: {gte: since}, modelName: this.modelName, }, }; }
...
since = -1;
filter = {};
}
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
var model = this;
const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
...
createChangeStream = function (options, cb) { if (typeof options === 'function') { cb = options; options = undefined; } var idName = this.getIdName(); var Model = this; var changes = new PassThrough({objectMode: true}); var writeable = true; changes.destroy = function() { changes.removeAllListeners('error'); changes.removeAllListeners('end'); writeable = false; changes = null; }; changes.on('error', function() { writeable = false; }); changes.on('end', function() { writeable = false; }); process.nextTick(function() { cb(null, changes); }); Model.observe('after save', createChangeHandler('save')); Model.observe('after delete', createChangeHandler('delete')); function createChangeHandler(type) { return function(ctx, next) { // since it might have set to null via destroy if (!changes) { return next(); } var where = ctx.where; var data = ctx.instance || ctx.data; var whereId = where && where[idName]; // the data includes the id // or the where includes the id var target; if (data && (data[idName] || data[idName] === 0)) { target = data[idName]; } else if (where && (where[idName] || where[idName] === 0)) { target = where[idName]; } var hasTarget = target === 0 || !!target; var change = { target: target, where: where, data: data, }; switch (type) { case 'save': if (ctx.isNewInstance === undefined) { change.type = hasTarget ? 'update' : 'create'; } else { change.type = ctx.isNewInstance ? 'create' : 'update'; } break; case 'delete': change.type = 'remove'; break; } // TODO(ritch) this is ugly... maybe a ReadableStream would be better if (writeable) { changes.write(change); } next(); }; } }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
createUpdates = function (deltas, cb) { var Change = this.getChangeModel(); var updates = []; var Model = this; var tasks = []; deltas.forEach(function(change) { change = new Change(change); var type = change.type(); var update = {type: type, change: change}; switch (type) { case Change.CREATE: case Change.UPDATE: tasks.push(function(cb) { Model.findById(change.modelId, function(err, inst) { if (err) return cb(err); if (!inst) { return cb && cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); } else { update.data = inst; } updates.push(update); cb(); }); }); break; case Change.DELETE: updates.push(update); break; } }); async.parallel(tasks, function(err) { if (err) return cb(err); cb(null, updates); }); }
...
if (diff && diff.deltas && diff.deltas.length) {
debug('\tbuilding a list of updates');
utils.uploadInChunks(
diff.deltas,
replicationChunkSize,
function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback);
},
cb);
} else {
// nothing to replicate
done();
}
}
...
currentCheckpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.current(cb); }
n/a
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
diff = function (modelName, since, remoteChanges, callback) { callback = callback || utils.createPromiseCallback(); if (!Array.isArray(remoteChanges) || remoteChanges.length === 0) { callback(null, {deltas: [], conflicts: []}); return callback.promise; } var remoteChangeIndex = {}; var modelIds = []; remoteChanges.forEach(function(ch) { modelIds.push(ch.modelId); remoteChangeIndex[ch.modelId] = new Change(ch); }); // normalize `since` since = Number(since) || 0; this.find({ where: { modelName: modelName, modelId: {inq: modelIds}, }, }, function(err, allLocalChanges) { if (err) return callback(err); var deltas = []; var conflicts = []; var localModelIds = []; var localChanges = allLocalChanges.filter(function(c) { return c.checkpoint >= since; }); localChanges.forEach(function(localChange) { localChange = new Change(localChange); localModelIds.push(localChange.modelId); var remoteChange = remoteChangeIndex[localChange.modelId]; if (remoteChange && !localChange.equals(remoteChange)) { if (remoteChange.conflictsWith(localChange)) { remoteChange.debug('remote conflict'); localChange.debug('local conflict'); conflicts.push(localChange); } else { remoteChange.debug('remote delta'); deltas.push(remoteChange); } } }); modelIds.forEach(function(id) { if (localModelIds.indexOf(id) !== -1) return; var d = remoteChangeIndex[id]; var oldChange = allLocalChanges.filter(function(c) { return c.modelId === id; })[0]; if (oldChange) { d.prev = oldChange.rev; } else { d.prev = null; } deltas.push(d); }); callback(null, { deltas: deltas, conflicts: conflicts, }); }); return callback.promise; }
...
},
});
};
/**
* Get a set of deltas and conflicts since the given checkpoint.
*
* See [Change.diff()](#change-diff) for details.
*
* @param {Number} since Find deltas since this checkpoint.
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
...
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
enableChangeTracking = function () { var Model = this; var Change = this.Change || this._defineChangeModel(); var cleanupInterval = Model.settings.changeCleanupInterval || 30000; assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + ' is not attached to a dataSource'); var idName = this.getIdName(); var idProp = this.definition.properties[idName]; var idType = idProp && idProp.type; var idDefn = idProp && idProp.defaultFn; if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { deprecated('The model ' + this.modelName + ' is tracking changes, ' + 'which requires a string id with GUID/UUID default value.'); } Model.observe('after save', rectifyOnSave); Model.observe('after delete', rectifyOnDelete); // Only run if the run time is server // Can switch off cleanup by setting the interval to -1 if (runtime.isServer && cleanupInterval > 0) { // initial cleanup cleanup(); // cleanup setInterval(cleanup, cleanupInterval); } function cleanup() { Model.rectifyAllChanges(function(err) { if (err) { Model.handleChangeError(err, 'cleanup'); } }); } }
...
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
...
eventNames = function () { [native code] }
n/a
function exists(id, cb) { throwNotAttached(this.modelName, 'exists'); }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
function find(filter, cb) { throwNotAttached(this.modelName, 'find'); }
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, cb) { throwNotAttached(this.modelName, 'findById'); }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.Change.findById %s', id));
err.statusCode = 404;
...
findLastChange = function (id, cb) { var Change = this.getChangeModel(); Change.findOne({where: {modelId: id}}, cb); }
...
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
...
function findOne(filter, cb) { throwNotAttached(this.modelName, 'findOne'); }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, callback) { throwNotAttached(this.modelName, 'findOrCreate'); }
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
findOrCreateChange = function (modelName, modelId, callback) { assert(this.registry.findModel(modelName), modelName + ' does not exist'); callback = callback || utils.createPromiseCallback(); var id = this.idForModel(modelName, modelId); var Change = this; this.findById(id, function(err, change) { if (err) return callback(err); if (change) { callback(null, change); } else { var ch = new Change({ id: id, modelName: modelName, modelId: modelId, }); ch.debug('creating change'); Change.updateOrCreate(ch, callback); } }); return callback.promise; }
...
var Change = this;
var errors = [];
callback = callback || utils.createPromiseCallback();
var tasks = modelIds.map(function(id) {
return function(cb) {
Change.findOrCreateChange(modelName, id, function(err, change) {
if (err) return next(err);
change.rectify(next);
});
function next(err) {
if (err) {
err.modelName = modelName;
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getChangeModel = function () { var changeModel = this.Change; var isSetup = changeModel && changeModel.dataSource; assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); return changeModel; }
...
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback);
};
/**
* Get the changes to a model since the specified checkpoint. Provide a filter object
* to reduce the number of results returned.
* @param {Number} since Return only changes since this checkpoint.
...
getCheckpointModel = function () { var checkpointModel = this.Checkpoint; if (checkpointModel) return checkpointModel; // FIXME(bajtos) This code creates multiple different models with the same // model name, which is not a valid supported usage of juggler's API. this.Checkpoint = checkpointModel = loopback.Checkpoint.extend('checkpoint'); assert(this.dataSource, 'Cannot getCheckpointModel(): ' + this.modelName + ' is not attached to a dataSource'); checkpointModel.attachTo(this.dataSource); return checkpointModel; }
...
/**
* Create a checkpoint.
*
* @param {Function} callback
*/
PersistedModel.checkpoint = function(cb) {
var Checkpoint = this.getChangeModel().getCheckpointModel();
Checkpoint.bumpLastSeq(cb);
};
/**
* Get the current checkpoint ID.
*
* @callback {Function} callback Callback function called with `(err, currentCheckpointId)` arguments. Required.
...
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getIdName = function () { var Model = this; var ds = Model.getDataSource(); if (ds.idName) { return ds.idName(Model.modelName); } else { return 'id'; } }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
getSourceId = function (cb) { var dataSource = this.dataSource; if (!dataSource) { this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); } assert( dataSource.connector.name, 'Model.getSourceId: cannot get id without dataSource.connector.name' ); var id = [dataSource.connector.name, this.modelName].join('-'); cb(null, id); }
n/a
handleChangeError = function (err, operationName) { if (!err) return; this.emit('error', err, operationName); }
...
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance;
...
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
hash = function (str) { return crypto .createHash(Change.settings.hashAlgorithm || 'sha1') .update(str) .digest('hex'); }
...
*
* @param {String} modelName
* @param {String} modelId
* @return {String}
*/
Change.idForModel = function(modelName, modelId) {
return this.hash([modelName, modelId].join('-'));
};
/**
* Find or create a change for the given model.
*
* @param {String} modelName
* @param {String} modelId
...
idForModel = function (modelName, modelId) { return this.hash([modelName, modelId].join('-')); }
...
PersistedModel.setup.call(this);
var Change = this;
Change.getter.id = function() {
var hasModel = this.modelName && this.modelId;
if (!hasModel) return null;
return Change.idForModel(this.modelName, this.modelId);
};
};
Change.setup();
/**
* Track the recent change of the given modelIds.
*
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
n/a
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
rectifyAll = function (cb) { debug('rectify all'); var Change = this; // this should be optimized this.find(function(err, changes) { if (err) return cb(err); async.each( changes, function(c, next) { c.rectify(next); }, cb); }); }
...
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
/**
* Handle a change error. Override this method in a subclassing model to customize
* change error handling.
*
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
...
rectifyAllChanges = function (callback) { this.getChangeModel().rectifyAll(callback); }
...
cleanup();
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
...
rectifyChange = function (id, callback) { var Change = this.getChangeModel(); Change.rectifyModelChanges(this.modelName, [id], callback); }
...
debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),
ctx.Model.modelName, id ? id : 'ALL');
debug('context instance:%j currentInstance:%j where:%j data %j',
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
}
if (id) {
ctx.Model.rectifyChange(id, reportErrorAndNext);
} else {
ctx.Model.rectifyAllChanges(reportErrorAndNext);
}
function reportErrorAndNext(err) {
if (err) {
ctx.Model.handleChangeError(err, 'after save');
...
rectifyModelChanges = function (modelName, modelIds, callback) { var Change = this; var errors = []; callback = callback || utils.createPromiseCallback(); var tasks = modelIds.map(function(id) { return function(cb) { Change.findOrCreateChange(modelName, id, function(err, change) { if (err) return next(err); change.rectify(next); }); function next(err) { if (err) { err.modelName = modelName; err.modelId = id; errors.push(err); } cb(); } }; }); async.parallel(tasks, function(err) { if (err) return callback(err); if (errors.length) { var desc = errors .map(function(e) { return '#' + e.modelId + ' - ' + e.toString(); }) .join('\n'); var msg = g.f('Cannot rectify %s changes:\n%s', modelName, desc); err = new Error(msg); err.details = {errors: errors}; return callback(err); } callback(); }); return callback.promise; }
...
if (rev !== change.prev) {
debug('Detected non-rectified change of %s %j',
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
// TODO(bajtos) modify `data` so that it instructs
// the connector to remove any properties included in "inst"
// but not included in `data`
// See https://github.com/strongloop/loopback/issues/1215
...
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
n/a
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
function replaceById(id, data, cb) { throwNotAttached(this.modelName, 'replaceById'); }
n/a
function replaceOrCreate(data, callback) { throwNotAttached(this.modelName, 'replaceOrCreate'); }
n/a
replicate = function (since, targetModel, options, callback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof since === 'function' && since.modelName) { targetModel = since; since = -1; } if (typeof since !== 'object') { since = {source: since, target: since}; } if (typeof options === 'function') { options = {}; } options = options || {}; var sourceModel = this; callback = callback || utils.createPromiseCallback(); debug('replicating %s since %s to %s since %s', sourceModel.modelName, since.source, targetModel.modelName, since.target); if (options.filter) { debug('\twith filter %j', options.filter); } // In order to avoid a race condition between the replication and // other clients modifying the data, we must create the new target // checkpoint as the first step of the replication process. // As a side-effect of that, the replicated changes are associated // with the new target checkpoint. This is actually desired behaviour, // because that way clients replicating *from* the target model // since the new checkpoint will pick these changes up. // However, it increases the likelihood of (false) conflicts being detected. // In order to prevent that, we run the replication multiple times, // until no changes were replicated, but at most MAX_ATTEMPTS times // to prevent starvation. In most cases, the second run will find no changes // to replicate and we are done. var MAX_ATTEMPTS = 3; run(1, since); return callback.promise; function run(attempt, since) { debug('\titeration #%s', attempt); tryReplicate(sourceModel, targetModel, since, options, next); function next(err, conflicts, cps, updates) { var finished = err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS; if (finished) return callback(err, conflicts, cps); run(attempt + 1, cps); } } }
n/a
revisionForInst = function (inst) { assert(inst, 'Change.revisionForInst() requires an instance object.'); return this.hash(CJSON.stringify(inst)); }
...
});
callback(null, dataLookup);
});
}
function applyUpdate(Model, id, current, data, change, conflicts, options, cb) {
var Change = Model.getChangeModel();
var rev = current ? Change.revisionForInst(current) : null;
if (rev !== change.prev) {
debug('Detected non-rectified change of %s %j',
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
...
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
setup = function () { PersistedModel.setup.call(this); var Change = this; Change.getter.id = function() { var hasModel = this.modelName && this.modelId; if (!hasModel) return null; return Change.idForModel(this.modelName, this.modelId); }; }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupRemoting = function () { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; // This is just for LB 3.x options.replaceOnPUT = options.replaceOnPUT !== false; function setRemoting(scope, name, options) { var fn = scope[name]; fn._delegate = true; options.isStatic = scope === PersistedModel; PersistedModel.remoteMethod(name, options); } setRemoting(PersistedModel, 'create', { description: 'Create a new instance of the model and persist it into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, allowArray: true, description: 'Model instance data', http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/'}, }); var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], description: 'Patch an existing model instance or insert a new one ' + 'into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'patch', path: '/'}], }; if (!options.replaceOnPUT) { upsertOptions.http.unshift({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); var replaceOrCreateOptions = { description: 'Replace an existing model instance or insert a new one into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'post', path: '/replaceOrCreate'}], }; if (options.replaceOnPUT) { replaceOrCreateOptions.http.push({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'upsertWithWhere', { aliases: ['patchOrCreateWithWhere'], description: 'Update an existing model instance or insert a new one into ' + 'the data source based on the where criteria.', accessType: 'WRITE', accepts: [ {arg: 'where', type: 'object', http: {source: 'query'}, description: 'Criteria to match model instances'}, {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'An object of model property name/value pairs'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/upsertWithWhere'}, }); setRemoting(PersistedModel, 'exists', { description: 'Check whether a model instance exists in the data source.', accessType: 'READ', accepts: [ {arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'exists', type: 'boolean'}, http: [ {verb: 'get', path: '/:id/exists'}, {verb: 'head', path: '/:id'}, ], rest: { // After hook to map exists to 200/404 for HEAD after: function(ctx, cb) { if (ctx.req.method === 'GET') { // For GET, return {exists: true|false} as is return cb(); } if (!ctx.result.exists) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; cb(error); } else { cb(); } ...
...
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
/*!
* Throw an error telling the user that the method is not available and why.
*/
function throwNotAttached(modelName, methodName) {
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.Change.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.Change.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
/**
* 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'}, ...}
...
updateLastChange = function (id, data, cb) { var self = this; this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { err = new Error(g.f('No change record found for %s with id %s', self.modelName, id)); err.statusCode = 404; return cb(err); } inst.updateAttributes(data, cb); }); }
...
Conflict.prototype.resolve = function(cb) {
var conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{prev: targetChange.rev},
cb);
});
};
/**
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
function Conflict(modelId, SourceModel, TargetModel) { this.SourceModel = SourceModel; this.TargetModel = TargetModel; this.SourceChange = SourceModel.getChangeModel(); this.TargetChange = TargetModel.getChangeModel(); this.modelId = modelId; }
...
debug('\treplication finished');
debug('\t\t%s conflict(s) detected', diff.conflicts.length);
debug('\t\t%s change(s) applied', updates ? updates.length : 0);
debug('\t\tnew checkpoints: { source: %j, target: %j }',
newSourceCp, newTargetCp);
var conflicts = diff.conflicts.map(function(change) {
return new Change.Conflict(
change.modelId, sourceModel, targetModel
);
});
if (conflicts.length) {
sourceModel.emit('conflicts', conflicts);
}
...
changes = function (cb) { var conflict = this; var sourceChange, targetChange; async.parallel([ getSourceChange, getTargetChange, ], done); function getSourceChange(cb) { var SourceModel = conflict.SourceModel; SourceModel.findLastChange(conflict.modelId, function(err, change) { if (err) return cb(err); sourceChange = change; cb(); }); } function getTargetChange(cb) { var TargetModel = conflict.TargetModel; TargetModel.findLastChange(conflict.modelId, function(err, change) { if (err) return cb(err); targetChange = change; cb(); }); } function done(err) { if (err) return cb(err); cb(null, sourceChange, targetChange); } }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
models = function (cb) { var conflict = this; var SourceModel = this.SourceModel; var TargetModel = this.TargetModel; var source, target; async.parallel([ getSourceModel, getTargetModel, ], done); function getSourceModel(cb) { SourceModel.findById(conflict.modelId, function(err, model) { if (err) return cb(err); source = model; cb(); }); } function getTargetModel(cb) { TargetModel.findById(conflict.modelId, function(err, model) { if (err) return cb(err); target = model; cb(); }); } function done(err) { if (err) return cb(err); cb(null, source, target); } }
...
}
var modelName = Model.modelName;
this.models[modelName] =
this.models[classify(modelName)] =
this.models[camelize(modelName)] = Model;
this.models().push(Model);
if (isPublic && Model.sharedClass) {
this.remotes().defineObjectType(Model.modelName, function(data) {
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
...
resolve = function (cb) { var conflict = this; conflict.TargetModel.findLastChange( this.modelId, function(err, targetChange) { if (err) return cb(err); conflict.SourceModel.updateLastChange( conflict.modelId, {prev: targetChange.rev}, cb); }); }
...
},
},
mochaTest: {
'unit': {
src: 'test/*.js',
options: {
reporter: 'dot',
require: require.resolve('./test/helpers/use-english.js'),
},
},
'unit-xml': {
src: 'test/*.js',
options: {
reporter: 'xunit',
captureFile: 'xunit.xml',
...
resolveManually = function (data, cb) { var conflict = this; if (!data) { return conflict.SourceModel.deleteById(conflict.modelId, done); } conflict.models(function(err, source, target) { if (err) return done(err); var inst = source || new conflict.SourceModel(target); inst.setAttributes(data); inst.save(function(err) { if (err) return done(err); conflict.resolve(done); }); }); function done(err) { // don't forward any cb arguments from internal calls cb(err); } }
n/a
resolveUsingSource = function (cb) { this.resolve(function(err) { // don't forward any cb arguments from resolve() cb(err); }); }
n/a
resolveUsingTarget = function (cb) { var conflict = this; conflict.models(function(err, source, target) { if (err) return done(err); if (target === null) { return conflict.SourceModel.deleteById(conflict.modelId, done); } var inst = new conflict.SourceModel( target.toObject(), {persisted: true}); inst.save(done); }); function done(err) { // don't forward any cb arguments from internal calls cb(err); } }
...
/**
* Return a new Conflict instance with swapped Source and Target models.
*
* This is useful when resolving a conflict in one-way
* replication, where the source data must not be changed:
*
* ```js
* conflict.swapParties().resolveUsingTarget(cb);
* ```
*
* @returns {Conflict} A new Conflict instance.
*/
Conflict.prototype.swapParties = function() {
var Ctor = this.constructor;
return new Ctor(this.modelId, this.TargetModel, this.SourceModel);
...
swapParties = function () { var Ctor = this.constructor; return new Ctor(this.modelId, this.TargetModel, this.SourceModel); }
...
/**
* Return a new Conflict instance with swapped Source and Target models.
*
* This is useful when resolving a conflict in one-way
* replication, where the source data must not be changed:
*
* ```js
* conflict.swapParties().resolveUsingTarget(cb);
* ```
*
* @returns {Conflict} A new Conflict instance.
*/
Conflict.prototype.swapParties = function() {
var Ctor = this.constructor;
return new Ctor(this.modelId, this.TargetModel, this.SourceModel);
...
type = function (cb) { var conflict = this; this.changes(function(err, sourceChange, targetChange) { if (err) return cb(err); var sourceChangeType = sourceChange.type(); var targetChangeType = targetChange.type(); if (sourceChangeType === Change.UPDATE && targetChangeType === Change.UPDATE) { return cb(null, Change.UPDATE); } if (sourceChangeType === Change.DELETE || targetChangeType === Change.DELETE) { return cb(null, Change.DELETE); } return cb(null, Change.UNKNOWN); }); }
...
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
if (err) return callback(err);
var modelIds = models.map(function(m) {
return m[idName].toString();
});
callback(null, changes.filter(function(ch) {
if (ch.type() === Change.DELETE) return true;
return modelIds.indexOf(ch.modelId) > -1;
}));
});
});
};
/**
...
id = function () { var hasModel = this.modelName && this.modelId; if (!hasModel) return null; return Change.idForModel(this.modelName, this.modelId); }
n/a
conflictsWith = function (change) { if (!change) return false; if (this.equals(change)) return false; if (Change.bothDeleted(this, change)) return false; if (this.isBasedOn(change)) return false; return true; }
...
});
localChanges.forEach(function(localChange) {
localChange = new Change(localChange);
localModelIds.push(localChange.modelId);
var remoteChange = remoteChangeIndex[localChange.modelId];
if (remoteChange && !localChange.equals(remoteChange)) {
if (remoteChange.conflictsWith(localChange)) {
remoteChange.debug('remote conflict');
localChange.debug('local conflict');
conflicts.push(localChange);
} else {
remoteChange.debug('remote delta');
deltas.push(remoteChange);
}
...
currentRevision = function (cb) { cb = cb || utils.createPromiseCallback(); var model = this.getModelCtor(); var id = this.getModelId(); model.findById(id, function(err, inst) { if (err) return cb(err); if (inst) { cb(null, Change.revisionForInst(inst)); } else { cb(null, null); } }); return cb.promise; }
n/a
debug = function () { if (debug.enabled) { var args = Array.prototype.slice.call(arguments); args[0] = args[0] + ' %s'; args.push(this.modelName); debug.apply(this, args); debug('\tid', this.id); debug('\trev', this.rev); debug('\tprev', this.prev); debug('\tcheckpoint', this.checkpoint); debug('\tmodelName', this.modelName); debug('\tmodelId', this.modelId); debug('\ttype', this.type()); } }
...
}
}
}
if (debug.enabled) {
debug('The following ACLs were searched: ');
acls.forEach(function(acl) {
acl.debug();
debug('with score:', acl.score(req));
});
}
var res = new AccessRequest({
model: req.model,
property: req.property,
accessType: req.accessType,
...
equals = function (change) { if (!change) return false; var thisRev = this.rev || null; var thatRev = change.rev || null; return thisRev === thatRev; }
...
* @param {String} [principalName] The principal name
* @returns {boolean}
*/
AccessContext.prototype.addPrincipal = function(principalType, principalId, principalName) {
var principal = new Principal(principalType, principalId, principalName);
for (var i = 0; i < this.principals.length; i++) {
var p = this.principals[i];
if (p.equals(principal)) {
return false;
}
}
this.principals.push(principal);
return true;
};
...
getModel = function (callback) { var Model = this.constructor.settings.trackModel; var id = this.getModelId(); Model.findById(id, callback); }
...
context = context || {};
assert(context.registry,
'Application registry is mandatory in AccessContext but missing in provided context');
this.registry = context.registry;
this.principals = context.principals || [];
var model = context.model;
model = ('string' === typeof model) ? this.registry.getModel(model) : model
;
this.model = model;
this.modelName = model && model.modelName;
this.modelId = context.id || context.modelId;
this.property = context.property || AccessContext.ALL;
this.method = context.method;
...
getModelCtor = function () { return this.constructor.settings.trackModel; }
...
var change = this;
var currentRev = this.rev;
change.debug('rectify change');
cb = cb || utils.createPromiseCallback();
const model = this.getModelCtor();
const id = this.getModelId();
model.findById(id, function(err, inst) {
if (err) return cb(err);
if (inst) {
inst.fillCustomChangeProperties(change, function() {
...
getModelId = function () { // TODO(ritch) get rid of the need to create an instance var Model = this.getModelCtor(); var id = this.modelId; var m = new Model(); m.setId(id); return m.getId(); }
...
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
if (err) return callback(err);
var modelIds = models.map(function(m) {
return m[idName].toString();
});
...
isBasedOn = function (change) { return this.prev === change.rev; }
...
* @return {Boolean}
*/
Change.prototype.conflictsWith = function(change) {
if (!change) return false;
if (this.equals(change)) return false;
if (Change.bothDeleted(this, change)) return false;
if (this.isBasedOn(change)) return false;
return true;
};
/**
* Are both changes deletes?
* @param {Change} a
* @param {Change} b
...
rectify = function (cb) { var change = this; var currentRev = this.rev; change.debug('rectify change'); cb = cb || utils.createPromiseCallback(); const model = this.getModelCtor(); const id = this.getModelId(); model.findById(id, function(err, inst) { if (err) return cb(err); if (inst) { inst.fillCustomChangeProperties(change, function() { const rev = Change.revisionForInst(inst); prepareAndDoRectify(rev); }); } else { prepareAndDoRectify(null); } }); return cb.promise; function prepareAndDoRectify(rev) { // avoid setting rev and prev to the same value if (currentRev === rev) { change.debug('rev and prev are equal (not updating anything)'); return cb(null, change); } // FIXME(@bajtos) Allow callers to pass in the checkpoint value // (or even better - a memoized async function to get the cp value) // That will enable `rectifyAll` to cache the checkpoint value change.constructor.getCheckpointModel().current( function(err, checkpoint) { if (err) return cb(err); doRectify(checkpoint, rev); } ); } function doRectify(checkpoint, rev) { if (rev) { if (currentRev === rev) { change.debug('ASSERTION FAILED: Change currentRev==rev ' + 'should have been already handled'); return cb(null, change); } else { change.rev = rev; change.debug('updated revision (was ' + currentRev + ')'); if (change.checkpoint !== checkpoint) { // previous revision is updated only across checkpoints change.prev = currentRev; change.debug('updated prev'); } } } else { change.rev = null; change.debug('updated revision (was ' + currentRev + ')'); if (change.checkpoint !== checkpoint) { // previous revision is updated only across checkpoints if (currentRev) { change.prev = currentRev; } else if (!change.prev) { change.debug('ERROR - could not determine prev'); change.prev = Change.UNKNOWN; } change.debug('updated prev'); } } if (change.checkpoint != checkpoint) { debug('update checkpoint to', checkpoint); change.checkpoint = checkpoint; } if (change.prev === Change.UNKNOWN) { // this occurs when a record of a change doesn't exist // and its current revision is null (not found) change.remove(cb); } else { change.save(cb); } } }
...
callback = callback || utils.createPromiseCallback();
var tasks = modelIds.map(function(id) {
return function(cb) {
Change.findOrCreateChange(modelName, id, function(err, change) {
if (err) return next(err);
change.rectify(next);
});
function next(err) {
if (err) {
err.modelName = modelName;
err.modelId = id;
errors.push(err);
...
type = function () { if (this.rev && this.prev) { return Change.UPDATE; } if (this.rev && !this.prev) { return Change.CREATE; } if (!this.rev && this.prev) { return Change.DELETE; } return Change.UNKNOWN; }
...
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
if (err) return callback(err);
var modelIds = models.map(function(m) {
return m[idName].toString();
});
callback(null, changes.filter(function(ch) {
if (ch.type() === Change.DELETE) return true;
return modelIds.indexOf(ch.modelId) > -1;
}));
});
});
};
/**
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_defineChangeModel = function () { var BaseChangeModel = this.registry.getModel('Change'); assert(BaseChangeModel, 'Change model must be defined before enabling change replication'); const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {}; this.Change = BaseChangeModel.extend(this.modelName + '-change', additionalChangeModelProperties, {trackModel: this} ); if (this.dataSource) { attachRelatedModels(this); } // Re-attach related models whenever our datasource is changed. var self = this; this.on('dataSourceAttached', function() { attachRelatedModels(self); }); return this.Change; function attachRelatedModels(self) { self.Change.attachTo(self.dataSource); self.Change.getCheckpointModel().attachTo(self.dataSource); } }
...
// call Model.setup first
Model.setup.call(this);
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_getSingleton = function (cb) { var query = {limit: 1}; // match all instances, return only one var initialData = {seq: 1}; this.findOrCreate(query, initialData, cb); }
...
* Get the current checkpoint id
* @callback {Function} callback
* @param {Error} err
* @param {Number} checkpoint The current checkpoint seq
*/
Checkpoint.current = function(cb) {
var Checkpoint = this;
Checkpoint._getSingleton(function(err, cp) {
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
bulkUpdate = function (updates, options, callback) { var tasks = []; var Model = this; var Change = this.getChangeModel(); var conflicts = []; var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof options === 'function') { options = {}; } options = options || {}; buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) { if (err) return callback(err); updates.forEach(function(update) { var id = update.change.modelId; var current = currentMap[id]; switch (update.type) { case Change.UPDATE: tasks.push(function(cb) { applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.CREATE: tasks.push(function(cb) { applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.DELETE: tasks.push(function(cb) { applyDelete(Model, id, current, update.change, conflicts, options, cb); }); break; } }); async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = {conflicts: conflicts}; return callback(err); } callback(); }); }); }
...
function bulkUpdate(_updates, cb) {
debug('\tstarting bulk update');
updates = _updates;
utils.uploadInChunks(
updates,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.bulkUpdate(smallArray, options, function(err) {
// bulk update is a special case where we want to process all chunks and aggregate all errors
chunkCallback(null, err);
});
},
function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) {
...
bumpLastSeq = function (cb) { var Checkpoint = this; Checkpoint._getSingleton(function(err, cp) { if (err) return cb(err); var originalSeq = cp.seq; cp.seq++; // Update the checkpoint but only if it was not changed under our hands Checkpoint.updateAll({id: cp.id, seq: originalSeq}, {seq: cp.seq}, function(err, info) { if (err) return cb(err); // possible outcomes // 1) seq was updated to seq+1 - exactly what we wanted! // 2) somebody else already updated seq to seq+1 and our call was a no-op. // That should be ok, checkpoints are time based, so we reuse the one created just now // 3) seq was bumped more than once, so we will be using a value that is behind the latest seq. // @bajtos is not entirely sure if this is ok, but since it wasn't handled by the current implementation either, // he thinks we can keep it this way. cb(null, cp); }); }); }
...
* Create a checkpoint.
*
* @param {Function} callback
*/
PersistedModel.checkpoint = function(cb) {
var Checkpoint = this.getChangeModel().getCheckpointModel();
Checkpoint.bumpLastSeq(cb);
};
/**
* Get the current checkpoint ID.
*
* @callback {Function} callback Callback function called with `(err, currentCheckpointId)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
...
changes = function (since, filter, callback) { if (typeof since === 'function') { filter = {}; callback = since; since = -1; } if (typeof filter === 'function') { callback = filter; since = -1; filter = {}; } var idName = this.dataSource.idName(this.modelName); var Change = this.getChangeModel(); var model = this; const changeFilter = this.createChangeFilter(since, filter); filter = filter || {}; filter.fields = {}; filter.where = filter.where || {}; filter.fields[idName] = true; // TODO(ritch) this whole thing could be optimized a bit more Change.find(changeFilter, function(err, changes) { if (err) return callback(err); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); var ids = changes.map(function(change) { return change.getModelId(); }); filter.where[idName] = {inq: ids}; model.find(filter, function(err, models) { if (err) return callback(err); var modelIds = models.map(function(m) { return m[idName].toString(); }); callback(null, changes.filter(function(ch) { if (ch.type() === Change.DELETE) return true; return modelIds.indexOf(ch.modelId) > -1; })); }); }); }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
checkpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.bumpLastSeq(cb); }
...
}
cb(err);
});
}
function checkpoints() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) {
if (err) return cb(err);
newSourceCp = source.seq;
targetModel.checkpoint(function(err, target) {
if (err) return cb(err);
newTargetCp = target.seq;
debug('\tcreated checkpoints');
debug('\t\t%s for source model %s', newSourceCp, sourceModel.modelName);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
count = function (where, cb) { throwNotAttached(this.modelName, 'count'); }
n/a
create = function (data, callback) { throwNotAttached(this.modelName, 'create'); }
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
createChangeFilter = function (since, modelFilter) { return { where: { checkpoint: {gte: since}, modelName: this.modelName, }, }; }
...
since = -1;
filter = {};
}
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
var model = this;
const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
...
createChangeStream = function (options, cb) { if (typeof options === 'function') { cb = options; options = undefined; } var idName = this.getIdName(); var Model = this; var changes = new PassThrough({objectMode: true}); var writeable = true; changes.destroy = function() { changes.removeAllListeners('error'); changes.removeAllListeners('end'); writeable = false; changes = null; }; changes.on('error', function() { writeable = false; }); changes.on('end', function() { writeable = false; }); process.nextTick(function() { cb(null, changes); }); Model.observe('after save', createChangeHandler('save')); Model.observe('after delete', createChangeHandler('delete')); function createChangeHandler(type) { return function(ctx, next) { // since it might have set to null via destroy if (!changes) { return next(); } var where = ctx.where; var data = ctx.instance || ctx.data; var whereId = where && where[idName]; // the data includes the id // or the where includes the id var target; if (data && (data[idName] || data[idName] === 0)) { target = data[idName]; } else if (where && (where[idName] || where[idName] === 0)) { target = where[idName]; } var hasTarget = target === 0 || !!target; var change = { target: target, where: where, data: data, }; switch (type) { case 'save': if (ctx.isNewInstance === undefined) { change.type = hasTarget ? 'update' : 'create'; } else { change.type = ctx.isNewInstance ? 'create' : 'update'; } break; case 'delete': change.type = 'remove'; break; } // TODO(ritch) this is ugly... maybe a ReadableStream would be better if (writeable) { changes.write(change); } next(); }; } }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
createUpdates = function (deltas, cb) { var Change = this.getChangeModel(); var updates = []; var Model = this; var tasks = []; deltas.forEach(function(change) { change = new Change(change); var type = change.type(); var update = {type: type, change: change}; switch (type) { case Change.CREATE: case Change.UPDATE: tasks.push(function(cb) { Model.findById(change.modelId, function(err, inst) { if (err) return cb(err); if (!inst) { return cb && cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); } else { update.data = inst; } updates.push(update); cb(); }); }); break; case Change.DELETE: updates.push(update); break; } }); async.parallel(tasks, function(err) { if (err) return cb(err); cb(null, updates); }); }
...
if (diff && diff.deltas && diff.deltas.length) {
debug('\tbuilding a list of updates');
utils.uploadInChunks(
diff.deltas,
replicationChunkSize,
function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback);
},
cb);
} else {
// nothing to replicate
done();
}
}
...
current = function (cb) { var Checkpoint = this; Checkpoint._getSingleton(function(err, cp) { cb(err, cp.seq); }); }
...
* @callback {Function} callback Callback function called with `(err, currentCheckpointId)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Number} currentCheckpointId Current checkpoint ID.
*/
PersistedModel.currentCheckpoint = function(cb) {
var Checkpoint = this.getChangeModel().getCheckpointModel();
Checkpoint.current(cb);
};
/**
* Replicate changes since the given checkpoint to the given target model.
*
* @param {Number} [since] Since this checkpoint
* @param {Model} targetModel Target this model class
...
currentCheckpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.current(cb); }
n/a
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
diff = function (since, remoteChanges, callback) { var Change = this.getChangeModel(); Change.diff(this.modelName, since, remoteChanges, callback); }
...
},
});
};
/**
* Get a set of deltas and conflicts since the given checkpoint.
*
* See [Change.diff()](#change-diff) for details.
*
* @param {Number} since Find deltas since this checkpoint.
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
...
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
enableChangeTracking = function () { var Model = this; var Change = this.Change || this._defineChangeModel(); var cleanupInterval = Model.settings.changeCleanupInterval || 30000; assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + ' is not attached to a dataSource'); var idName = this.getIdName(); var idProp = this.definition.properties[idName]; var idType = idProp && idProp.type; var idDefn = idProp && idProp.defaultFn; if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { deprecated('The model ' + this.modelName + ' is tracking changes, ' + 'which requires a string id with GUID/UUID default value.'); } Model.observe('after save', rectifyOnSave); Model.observe('after delete', rectifyOnDelete); // Only run if the run time is server // Can switch off cleanup by setting the interval to -1 if (runtime.isServer && cleanupInterval > 0) { // initial cleanup cleanup(); // cleanup setInterval(cleanup, cleanupInterval); } function cleanup() { Model.rectifyAllChanges(function(err) { if (err) { Model.handleChangeError(err, 'cleanup'); } }); } }
...
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
...
eventNames = function () { [native code] }
n/a
function exists(id, cb) { throwNotAttached(this.modelName, 'exists'); }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
function find(filter, cb) { throwNotAttached(this.modelName, 'find'); }
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, cb) { throwNotAttached(this.modelName, 'findById'); }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.Checkpoint.findById %s', id));
err.statusCode = 404;
...
findLastChange = function (id, cb) { var Change = this.getChangeModel(); Change.findOne({where: {modelId: id}}, cb); }
...
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
...
function findOne(filter, cb) { throwNotAttached(this.modelName, 'findOne'); }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, callback) { throwNotAttached(this.modelName, 'findOrCreate'); }
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getChangeModel = function () { var changeModel = this.Change; var isSetup = changeModel && changeModel.dataSource; assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); return changeModel; }
...
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback);
};
/**
* Get the changes to a model since the specified checkpoint. Provide a filter object
* to reduce the number of results returned.
* @param {Number} since Return only changes since this checkpoint.
...
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getIdName = function () { var Model = this; var ds = Model.getDataSource(); if (ds.idName) { return ds.idName(Model.modelName); } else { return 'id'; } }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
getSourceId = function (cb) { var dataSource = this.dataSource; if (!dataSource) { this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); } assert( dataSource.connector.name, 'Model.getSourceId: cannot get id without dataSource.connector.name' ); var id = [dataSource.connector.name, this.modelName].join('-'); cb(null, id); }
n/a
handleChangeError = function (err, operationName) { if (!err) return; this.emit('error', err, operationName); }
...
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance;
...
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
n/a
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
rectifyAllChanges = function (callback) { this.getChangeModel().rectifyAll(callback); }
...
cleanup();
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
...
rectifyChange = function (id, callback) { var Change = this.getChangeModel(); Change.rectifyModelChanges(this.modelName, [id], callback); }
...
debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),
ctx.Model.modelName, id ? id : 'ALL');
debug('context instance:%j currentInstance:%j where:%j data %j',
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
}
if (id) {
ctx.Model.rectifyChange(id, reportErrorAndNext);
} else {
ctx.Model.rectifyAllChanges(reportErrorAndNext);
}
function reportErrorAndNext(err) {
if (err) {
ctx.Model.handleChangeError(err, 'after save');
...
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
n/a
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
function replaceById(id, data, cb) { throwNotAttached(this.modelName, 'replaceById'); }
n/a
function replaceOrCreate(data, callback) { throwNotAttached(this.modelName, 'replaceOrCreate'); }
n/a
replicate = function (since, targetModel, options, callback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof since === 'function' && since.modelName) { targetModel = since; since = -1; } if (typeof since !== 'object') { since = {source: since, target: since}; } if (typeof options === 'function') { options = {}; } options = options || {}; var sourceModel = this; callback = callback || utils.createPromiseCallback(); debug('replicating %s since %s to %s since %s', sourceModel.modelName, since.source, targetModel.modelName, since.target); if (options.filter) { debug('\twith filter %j', options.filter); } // In order to avoid a race condition between the replication and // other clients modifying the data, we must create the new target // checkpoint as the first step of the replication process. // As a side-effect of that, the replicated changes are associated // with the new target checkpoint. This is actually desired behaviour, // because that way clients replicating *from* the target model // since the new checkpoint will pick these changes up. // However, it increases the likelihood of (false) conflicts being detected. // In order to prevent that, we run the replication multiple times, // until no changes were replicated, but at most MAX_ATTEMPTS times // to prevent starvation. In most cases, the second run will find no changes // to replicate and we are done. var MAX_ATTEMPTS = 3; run(1, since); return callback.promise; function run(attempt, since) { debug('\titeration #%s', attempt); tryReplicate(sourceModel, targetModel, since, options, next); function next(err, conflicts, cps, updates) { var finished = err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS; if (finished) return callback(err, conflicts, cps); run(attempt + 1, cps); } } }
n/a
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
function setupPersistedModel() { // call Model.setup first Model.setup.call(this); var PersistedModel = this; // enable change tracking (usually for replication) if (this.settings.trackChanges) { PersistedModel._defineChangeModel(); PersistedModel.once('dataSourceAttached', function() { PersistedModel.enableChangeTracking(); }); } else if (this.settings.enableRemoteReplication) { PersistedModel._defineChangeModel(); } PersistedModel.setupRemoting(); }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupRemoting = function () { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; // This is just for LB 3.x options.replaceOnPUT = options.replaceOnPUT !== false; function setRemoting(scope, name, options) { var fn = scope[name]; fn._delegate = true; options.isStatic = scope === PersistedModel; PersistedModel.remoteMethod(name, options); } setRemoting(PersistedModel, 'create', { description: 'Create a new instance of the model and persist it into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, allowArray: true, description: 'Model instance data', http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/'}, }); var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], description: 'Patch an existing model instance or insert a new one ' + 'into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'patch', path: '/'}], }; if (!options.replaceOnPUT) { upsertOptions.http.unshift({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); var replaceOrCreateOptions = { description: 'Replace an existing model instance or insert a new one into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'post', path: '/replaceOrCreate'}], }; if (options.replaceOnPUT) { replaceOrCreateOptions.http.push({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'upsertWithWhere', { aliases: ['patchOrCreateWithWhere'], description: 'Update an existing model instance or insert a new one into ' + 'the data source based on the where criteria.', accessType: 'WRITE', accepts: [ {arg: 'where', type: 'object', http: {source: 'query'}, description: 'Criteria to match model instances'}, {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'An object of model property name/value pairs'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/upsertWithWhere'}, }); setRemoting(PersistedModel, 'exists', { description: 'Check whether a model instance exists in the data source.', accessType: 'READ', accepts: [ {arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'exists', type: 'boolean'}, http: [ {verb: 'get', path: '/:id/exists'}, {verb: 'head', path: '/:id'}, ], rest: { // After hook to map exists to 200/404 for HEAD after: function(ctx, cb) { if (ctx.req.method === 'GET') { // For GET, return {exists: true|false} as is return cb(); } if (!ctx.result.exists) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; cb(error); } else { cb(); } ...
...
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
/*!
* Throw an error telling the user that the method is not available and why.
*/
function throwNotAttached(modelName, methodName) {
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.Checkpoint.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.Checkpoint.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
/**
* 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'}, ...}
...
updateLastChange = function (id, data, cb) { var self = this; this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { err = new Error(g.f('No change record found for %s with id %s', self.modelName, id)); err.statusCode = 404; return cb(err); } inst.updateAttributes(data, cb); }); }
...
Conflict.prototype.resolve = function(cb) {
var conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{prev: targetChange.rev},
cb);
});
};
/**
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function Connector(options) { EventEmitter.apply(this, arguments); this.options = options; debug('created with options', options); }
n/a
_createJDBAdapter = function (jdbModule) { var fauxSchema = {}; jdbModule.initialize(fauxSchema, function() { // connected }); }
n/a
function EventEmitter() { EventEmitter.init.call(this); }
n/a
_addCrudOperationsFromJDBAdapter = function (connector) { }
n/a
function DataSource(name, settings, modelBuilder) { if (!(this instanceof DataSource)) { return new DataSource(name, settings); } // Check if the settings object is passed as the first argument if (typeof name === 'object' && settings === undefined) { settings = name; name = undefined; } // Check if the first argument is a URL if (typeof name === 'string' && name.indexOf('://') !== -1) { name = utils.parseSettings(name); } // Check if the settings is in the form of URL string if (typeof settings === 'string' && settings.indexOf('://') !== -1) { settings = utils.parseSettings(settings); } this.modelBuilder = modelBuilder || new ModelBuilder(); this.models = this.modelBuilder.models; this.definitions = this.modelBuilder.definitions; this.juggler = juggler; // operation metadata // Initialize it before calling setup as the connector might register operations this._operations = {}; this.setup(name, settings); this._setupConnector(); // connector var connector = this.connector; // DataAccessObject - connector defined or supply the default var dao = (connector && connector.DataAccessObject) || this.constructor.DataAccessObject; this.DataAccessObject = function() { }; // define DataAccessObject methods Object.keys(dao).forEach(function(name) { var fn = dao[name]; this.DataAccessObject[name] = fn; if (typeof fn === 'function') { this.defineOperation(name, { accepts: fn.accepts, 'returns': fn.returns, http: fn.http, remoteEnabled: fn.shared ? true : false, scope: this.DataAccessObject, fnName: name, }); } }.bind(this)); // define DataAccessObject.prototype methods Object.keys(dao.prototype).forEach(function(name) { var fn = dao.prototype[name]; this.DataAccessObject.prototype[name] = fn; if (typeof fn === 'function') { this.defineOperation(name, { prototype: true, accepts: fn.accepts, 'returns': fn.returns, http: fn.http, remoteEnabled: fn.shared ? true : false, scope: this.DataAccessObject.prototype, fnName: name, }); } }.bind(this)); }
n/a
function Any(value) { if (!(this instanceof Any)) { return value; } this.value = value; }
n/a
function DataAccessObject() { if (DataAccessObject._mixins) { var self = this; var args = arguments; DataAccessObject._mixins.forEach(function(m) { m.call(self, args); }); } }
n/a
function JSON(value) { if (!(this instanceof JSON)) { return value; } this.value = value; }
n/a
function Text(value) { if (!(this instanceof Text)) { return value; } this.value = value; }
n/a
_resolveConnector = function (name, loader) { var names = connectorModuleNames(name); var connector = tryModules(names, loader); var error = null; if (!connector) { error = g.f('\nWARNING: {{LoopBack}} connector "%s" is not installed ' + 'as any of the following modules:\n\n %s\n\nTo fix, run:\n\n {{npm install %s --save}}\n', name, names.join('\n'), names[names.length - 1]); } return { connector: connector, error: error, }; }
n/a
function EventEmitter() { EventEmitter.init.call(this); }
n/a
function Any(value) { if (!(this instanceof Any)) { return value; } this.value = value; }
n/a
toJSON = function () { return this.value; }
...
if (err) {
if (done) done(err);
return;
}
mappings.forEach(function(m) {
var role;
if (options.returnOnlyRoleNames === true) {
role = m.toJSON().role.name;
} else {
role = m.roleId;
}
addRole(role);
});
if (done) done();
});
...
toObject = function () { return this.value; }
...
options.validate = true;
}
if (!('throws' in options)) {
options.throws = false;
}
var inst = this;
var data = inst.toObject(true);
var id = this.getId();
if (!id) {
return Model.create(this, callback);
}
// validate first
...
function DataAccessObject() { if (DataAccessObject._mixins) { var self = this; var args = arguments; DataAccessObject._mixins.forEach(function(m) { m.call(self, args); }); } }
n/a
function Transaction(connector, connection) { this.connector = connector; this.connection = connection; EventEmitter.call(this); }
n/a
_allowExtendedOperators = function (options) { options = options || {}; var Model = this; var dsSettings = this.getDataSource().settings; var allowExtendedOperators = dsSettings.allowExtendedOperators; // options settings enable allowExtendedOperators per request (for example if // enable allowExtendedOperators only server side); // model settings enable allowExtendedOperators only for specific model. // dataSource settings enable allowExtendedOperators globally (all models); // options -> model -> dataSource (connector) if (options.hasOwnProperty('allowExtendedOperators')) { allowExtendedOperators = options.allowExtendedOperators === true; } else if (Model.settings && Model.settings.hasOwnProperty('allowExtendedOperators')) { allowExtendedOperators = Model.settings.allowExtendedOperators === true; } return allowExtendedOperators; }
n/a
_coerce = function (where, options) { var self = this; if (!where) { return where; } options = options || {}; var err; if (typeof where !== 'object' || Array.isArray(where)) { err = new Error(g.f('The where clause %j is not an {{object}}', where)); err.statusCode = 400; throw err; } var props = self.definition.properties; for (var p in where) { // Handle logical operators if (p === 'and' || p === 'or' || p === 'nor') { var clauses = where[p]; try { clauses = coerceArray(clauses); } catch (e) { err = new Error(g.f('The %s operator has invalid clauses %j: %s', p, clauses, e.message)); err.statusCode = 400; throw err; } for (var k = 0; k < clauses.length; k++) { self._coerce(clauses[k], options); } continue; } var DataType = props[p] && props[p].type; if (!DataType) { continue; } if (Array.isArray(DataType) || DataType === Array) { DataType = DataType[0]; } if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (DataType === Number) { // This fixes a regression in mongodb connector // For numbers, only convert it produces a valid number // LoopBack by default injects a number id. We should fix it based // on the connector's input, for example, MongoDB should use string // while RDBs typically use number DataType = NumberType; } if (!DataType) { continue; } if (DataType.prototype instanceof BaseModel) { continue; } if (DataType === geo.GeoPoint) { // Skip the GeoPoint as the near operator breaks the assumption that // an operation has only one property // We should probably fix it based on // http://docs.mongodb.org/manual/reference/operator/query/near/ // The other option is to make operators start with $ continue; } var val = where[p]; if (val === null || val === undefined) { continue; } // Check there is an operator var operator = null; var exp = val; if (val.constructor === Object) { for (var op in operators) { if (op in val) { val = val[op]; operator = op; switch (operator) { case 'inq': case 'nin': case 'between': try { val = coerceArray(val); } catch (e) { err = new Error(g.f('The %s property has invalid clause %j: %s', p, where[p], e)); err.statusCode = 400; throw err; } if (operator === 'between' && val.length !== 2) { err = new Error(g.f( 'The %s property has invalid clause %j: Expected precisely 2 values, received %d', p, where[p], val.length)); err.statusCode = 400; throw err; } break; case 'like': case 'nlike': case 'ilike': case 'nilike': if (!(typeof val === 'string' || val instanceof RegExp)) { err = new Error(g.f( 'The %s property has invalid clause %j: Expected a string or RegExp', p, where[p])); err.statusCode = 400; throw err; } break; case 'regexp': val = utils.toRegExp(val); if (val instanceof Error) { val.statusCode = 400; throw err; } break; } break; } } } try { // Coerce val into an array if it resembles an array-like object val = coerceArray(val); } catch (e) { // NOOP when not coercable into an array. } // Coerce the array items if (Array.isArray(val)) { for (var i = 0; i < val.length; i++) { if (v ...
n/a
_forDB = function (data) { if (!(this.getDataSource().isRelational && this.getDataSource().isRelational())) { return data; } var res = {}; for (var propName in data) { var type = this.getPropertyType(propName); if (type === 'JSON' || type === 'Any' || type === 'Object' || data[propName] instanceof Array) { res[propName] = JSON.stringify(data[propName]); } else { res[propName] = data[propName]; } } return res; }
n/a
_getSetting = function (key) { // Check for settings in model var m = this.definition; if (m && m.settings && m.settings[key]) { return m.settings[key]; } // Check for settings in connector var ds = this.getDataSource(); if (ds && ds.settings && ds.settings[key]) { return ds.settings[key]; } return; }
n/a
_normalize = function (filter, options) { if (!filter) { return undefined; } var err = null; if ((typeof filter !== 'object') || Array.isArray(filter)) { err = new Error(g.f('The query filter %j is not an {{object}}', filter)); err.statusCode = 400; throw err; } if (filter.limit || filter.skip || filter.offset) { var limit = Number(filter.limit || 100); var offset = Number(filter.skip || filter.offset || 0); if (isNaN(limit) || limit <= 0 || Math.ceil(limit) !== limit) { err = new Error(g.f('The {{limit}} parameter %j is not valid', filter.limit)); err.statusCode = 400; throw err; } if (isNaN(offset) || offset < 0 || Math.ceil(offset) !== offset) { err = new Error(g.f('The {{offset/skip}} parameter %j is not valid', filter.skip || filter.offset)); err.statusCode = 400; throw err; } filter.limit = limit; filter.offset = offset; filter.skip = offset; } if (filter.order) { var order = filter.order; if (!Array.isArray(order)) { order = [order]; } var fields = []; for (var i = 0, m = order.length; i < m; i++) { if (typeof order[i] === 'string') { // Normalize 'f1 ASC, f2 DESC, f3' to ['f1 ASC', 'f2 DESC', 'f3'] var tokens = order[i].split(/(?:\s*,\s*)+/); for (var t = 0, n = tokens.length; t < n; t++) { var token = tokens[t]; if (token.length === 0) { // Skip empty token continue; } var parts = token.split(/\s+/); if (parts.length >= 2) { var dir = parts[1].toUpperCase(); if (dir === 'ASC' || dir === 'DESC') { token = parts[0] + ' ' + dir; } else { err = new Error(g.f('The {{order}} %j has invalid direction', token)); err.statusCode = 400; throw err; } } fields.push(token); } } else { err = new Error(g.f('The order %j is not valid', order[i])); err.statusCode = 400; throw err; } } if (fields.length === 1 && typeof filter.order === 'string') { filter.order = fields[0]; } else { filter.order = fields; } } // normalize fields as array of included property names if (filter.fields) { filter.fields = fieldsToArray(filter.fields, Object.keys(this.definition.properties), this.settings.strict); } var handleUndefined = this._getSetting('normalizeUndefinedInQuery'); // alter configuration of how removeUndefined handles undefined values filter = removeUndefined(filter, handleUndefined); this._coerce(filter.where, options); return filter; }
n/a
all = function () { return DataAccessObject.find.apply(this, arguments); }
n/a
applyProperties = function (data, inst) { var properties = this.definition.settings.properties; properties = properties || this.definition.settings.attributes; if (typeof properties === 'object') { util._extend(data, properties); } else if (typeof properties === 'function') { util._extend(data, properties.call(this, data, inst) || {}); } else if (properties !== false) { var scope = this.defaultScope(data, inst) || {}; if (typeof scope.where === 'object') { setScopeValuesFromWhere(data, scope.where, this); } } }
n/a
applyScope = function (query, inst) { var scope = this.defaultScope(query, inst) || {}; if (typeof scope === 'object') { mergeQuery(query, scope || {}, this.definition.settings.scope); } }
n/a
beginTransaction = function (options, cb) { cb = cb || utils.createPromiseCallback(); if (Transaction) { var connector = this.getConnector(); Transaction.begin(connector, options, function(err, transaction) { if (err) return cb(err); if (transaction) { // Set an informational transaction id transaction.id = uuid.v1(); } if (options.timeout) { setTimeout(function() { var context = { transaction: transaction, operation: 'timeout', }; transaction.notifyObserversOf('timeout', context, function(err) { if (!err) { transaction.rollback(function() { debug('Transaction %s is rolled back due to timeout', transaction.id); }); } }); }, options.timeout); } cb(err, transaction); }); } else { process.nextTick(function() { var err = new Error(g.f('{{Transaction}} is not supported')); cb(err); }); } return cb.promise; }
n/a
belongsTo = function (modelTo, params) { return RelationDefinition.belongsTo(this, modelTo, params); }
n/a
count = function (where, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (options === undefined && cb === undefined) { if (typeof where === 'function') { // count(cb) cb = where; where = {}; } } else if (cb === undefined) { if (typeof options === 'function') { // count(where, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); where = where || {}; options = options || {}; assert(typeof where === 'object', 'The where argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var Model = this; var connector = Model.getConnector(); assert(typeof connector.count === 'function', 'count() must be implemented by the connector'); assert(connector.count.length >= 3, 'count() must take at least 3 arguments'); var hookState = {}; var query = {where: where}; this.applyScope(query); where = query.where; try { where = removeUndefined(where); where = this._coerce(where, options); } catch (err) { process.nextTick(function() { cb(err); }); return cb.promise; } var context = { Model: Model, query: {where: where}, hookState: hookState, options: options, }; this.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); where = ctx.query.where; if (connector.count.length <= 3) { // Old signature, please note where is the last // count(model, cb, where) connector.count(Model.modelName, cb, where); } else { // New signature // count(model, where, options, cb) connector.count(Model.modelName, where, options, cb); } }); return cb.promise; }
n/a
create = function (data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } var Model = this; var connector = Model.getConnector(); assert(typeof connector.create === 'function', 'create() must be implemented by the connector'); var self = this; if (options === undefined && cb === undefined) { if (typeof data === 'function') { // create(cb) cb = data; data = {}; } } else if (cb === undefined) { if (typeof options === 'function') { // create(data, cb); cb = options; options = {}; } } data = data || {}; options = options || {}; cb = cb || (Array.isArray(data) ? noCallback : utils.createPromiseCallback()); assert(typeof data === 'object', 'The data argument must be an object or array'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var hookState = {}; if (Array.isArray(data)) { // Undefined item will be skipped by async.map() which internally uses // Array.prototype.map(). The following loop makes sure all items are // iterated for (var i = 0, n = data.length; i < n; i++) { if (data[i] === undefined) { data[i] = {}; } } async.map(data, function(item, done) { self.create(item, options, function(err, result) { // Collect all errors and results done(null, {err: err, result: result || item}); }); }, function(err, results) { if (err) { return cb(err, results); } // Convert the results into two arrays var errors = null; var data = []; for (var i = 0, n = results.length; i < n; i++) { if (results[i].err) { if (!errors) { errors = []; } errors[i] = results[i].err; } data[i] = results[i].result; } cb(errors, data); }); return; } var enforced = {}; var obj; var idValue = getIdValue(this, data); // if we come from save if (data instanceof Model && !idValue) { obj = data; } else { obj = new Model(data); } this.applyProperties(enforced, obj); obj.setAttributes(enforced); Model = this.lookupModel(data); // data-specific if (Model !== obj.constructor) obj = new Model(data); var context = { Model: Model, instance: obj, isNewInstance: true, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err) { if (err) return cb(err); data = obj.toObject(true); // options has precedence on model-setting if (options.validate === false) { return create(); } // only when options.validate is not set, take model-setting into consideration if (options.validate === undefined && Model.settings.automaticValidation === false) { return create(); } // validation required obj.isValid(function(valid) { if (valid) { create(); } else { cb(new ValidationError(obj), obj); } }, data, options); }); function create() { obj.trigger('create', function(createDone) { obj.trigger('save', function(saveDone) { var _idName = idName(Model); var modelName = Model.modelName; var val = removeUndefined(obj.toObject(true)); function createCallback(err, id, rev) { if (id) { obj.__data[_idName] = id; defineReadonlyProp(obj, _idName, id); } if (rev) { obj._rev = rev; } if (err) { return cb(err, obj); } obj.__persisted = true; var context = { Model: Model, data: val, isNewInstance: true, hookState: hookState, options: options, }; Model.notifyObserversOf('loaded', context, function(err) { if (err) return cb(err); // By def ...
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
defaultScope = function (target, inst) { var scope = this.definition.settings.scope; if (typeof scope === 'function') { scope = this.definition.settings.scope.call(this, target, inst); } return scope; }
n/a
function destroyAll(where, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } var Model = this; var connector = Model.getConnector(); assert(typeof connector.destroyAll === 'function', 'destroyAll() must be implemented by the connector'); if (options === undefined && cb === undefined) { if (typeof where === 'function') { cb = where; where = {}; } } else if (cb === undefined) { if (typeof options === 'function') { cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); where = where || {}; options = options || {}; assert(typeof where === 'object', 'The where argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var hookState = {}; var query = {where: where}; this.applyScope(query); where = query.where; if (options.notify === false) { doDelete(where); } else { query = {where: whereIsEmpty(where) ? {} : where}; var context = { Model: Model, query: query, hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var context = { Model: Model, where: ctx.query.where, hookState: hookState, options: options, }; Model.notifyObserversOf('before delete', context, function(err, ctx) { if (err) return cb(err); doDelete(ctx.where); }); }); } function doDelete(where) { var context = { Model: Model, where: whereIsEmpty(where) ? {} : where, hookState: hookState, options: options, }; if (whereIsEmpty(where)) { if (connector.destroyAll.length === 4) { connector.destroyAll(Model.modelName, {}, options, done); } else { connector.destroyAll(Model.modelName, {}, done); } } else { try { // Support an optional where object where = removeUndefined(where); where = Model._coerce(where, options); } catch (err) { return process.nextTick(function() { cb(err); }); } if (connector.destroyAll.length === 4) { connector.destroyAll(Model.modelName, where, options, done); } else { connector.destroyAll(Model.modelName, where, done); } } function done(err, info) { if (err) return cb(err); if (options.notify === false) { return cb(err, info); } var context = { Model: Model, where: where, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after delete', context, function(err) { cb(err, info); }); } } return cb.promise; }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } assert(arguments.length >= 1, 'The id argument is required'); if (cb === undefined) { if (typeof options === 'function') { // destroyById(id, cb) cb = options; options = {}; } } options = options || {}; cb = cb || utils.createPromiseCallback(); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (isPKMissing(this, cb)) { return cb.promise; } else if (id == null || id === '') { process.nextTick(function() { cb(new Error(g.f('{{Model::deleteById}} requires the apidoc.element.loopback.DataSource.DataAccessObject.deleteById argument'))); }); return cb.promise; } var Model = this; this.remove(byIdQuery(this, id).where, options, function(err, info) { if (err) return cb(err); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { err = new Error(g.f('No instance with apidoc.element.loopback.DataSource.DataAccessObject.deleteById %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err); } cb(null, info); }); return cb.promise; }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } var Model = this; var connector = Model.getConnector(); assert(typeof connector.destroyAll === 'function', 'destroyAll() must be implemented by the connector'); if (options === undefined && cb === undefined) { if (typeof where === 'function') { cb = where; where = {}; } } else if (cb === undefined) { if (typeof options === 'function') { cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); where = where || {}; options = options || {}; assert(typeof where === 'object', 'The where argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var hookState = {}; var query = {where: where}; this.applyScope(query); where = query.where; if (options.notify === false) { doDelete(where); } else { query = {where: whereIsEmpty(where) ? {} : where}; var context = { Model: Model, query: query, hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var context = { Model: Model, where: ctx.query.where, hookState: hookState, options: options, }; Model.notifyObserversOf('before delete', context, function(err, ctx) { if (err) return cb(err); doDelete(ctx.where); }); }); } function doDelete(where) { var context = { Model: Model, where: whereIsEmpty(where) ? {} : where, hookState: hookState, options: options, }; if (whereIsEmpty(where)) { if (connector.destroyAll.length === 4) { connector.destroyAll(Model.modelName, {}, options, done); } else { connector.destroyAll(Model.modelName, {}, done); } } else { try { // Support an optional where object where = removeUndefined(where); where = Model._coerce(where, options); } catch (err) { return process.nextTick(function() { cb(err); }); } if (connector.destroyAll.length === 4) { connector.destroyAll(Model.modelName, where, options, done); } else { connector.destroyAll(Model.modelName, where, done); } } function done(err, info) { if (err) return cb(err); if (options.notify === false) { return cb(err, info); } var context = { Model: Model, where: where, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after delete', context, function(err) { cb(err, info); }); } } return cb.promise; }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } assert(arguments.length >= 1, 'The id argument is required'); if (cb === undefined) { if (typeof options === 'function') { // destroyById(id, cb) cb = options; options = {}; } } options = options || {}; cb = cb || utils.createPromiseCallback(); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (isPKMissing(this, cb)) { return cb.promise; } else if (id == null || id === '') { process.nextTick(function() { cb(new Error(g.f('{{Model::deleteById}} requires the apidoc.element.loopback.DataSource.DataAccessObject.destroyById argument'))); }); return cb.promise; } var Model = this; this.remove(byIdQuery(this, id).where, options, function(err, info) { if (err) return cb(err); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { err = new Error(g.f('No instance with apidoc.element.loopback.DataSource.DataAccessObject.destroyById %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err); } cb(null, info); }); return cb.promise; }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
function embedsMany(modelTo, params) { return RelationDefinition.embedsMany(this, modelTo, params); }
n/a
function embedsOne(modelTo, params) { return RelationDefinition.embedsOne(this, modelTo, params); }
n/a
function exists(id, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } assert(arguments.length >= 1, 'The id argument is required'); if (cb === undefined) { if (typeof options === 'function') { // exists(id, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); options = options || {}; assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (id !== undefined && id !== null && id !== '') { this.count(byIdQuery(this, id).where, options, function(err, count) { cb(err, err ? false : count === 1); }); } else { process.nextTick(function() { cb(new Error(g.f('{{Model::exists}} requires the apidoc.element.loopback.DataSource.DataAccessObject.exists argument'))); }); } return cb.promise; }
n/a
function find(query, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (options === undefined && cb === undefined) { if (typeof query === 'function') { // find(cb); cb = query; query = {}; } } else if (cb === undefined) { if (typeof options === 'function') { // find(query, cb); cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); query = query || {}; options = options || {}; assert(typeof query === 'object', 'The query argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var hookState = {}; var self = this; var connector = self.getConnector(); assert(typeof connector.all === 'function', 'all() must be implemented by the connector'); try { this._normalize(query, options); } catch (err) { process.nextTick(function() { cb(err); }); return cb.promise; } this.applyScope(query); var near = query && geo.nearFilter(query.where); var supportsGeo = !!connector.buildNearFilter; if (near) { if (supportsGeo) { // convert it connector.buildNearFilter(query, near); } else if (query.where) { // do in memory query // using all documents // TODO [fabien] use default scope here? if (options.notify === false) { queryGeo(query); } else { withNotifyGeo(); } function withNotifyGeo() { var context = { Model: self, query: query, hookState: hookState, options: options, }; self.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); queryGeo(ctx.query); }); } function queryGeo(query) { function geoCallbackWithoutNotify(err, data) { var memory = new Memory(); var modelName = self.modelName; if (err) { cb(err); } else if (Array.isArray(data)) { memory.define({ properties: self.dataSource.definitions[self.modelName].properties, settings: self.dataSource.definitions[self.modelName].settings, model: self, }); data.forEach(function(obj) { memory.create(modelName, obj, options, function() { // noop }); }); // FIXME: apply "includes" and other transforms - see allCb below memory.all(modelName, query, options, cb); } else { cb(null, []); } } function geoCallbackWithNotify(err, data) { if (err) return cb(err); async.map(data, function(item, next) { var context = { Model: self, data: item, isNewInstance: false, hookState: hookState, options: options, }; self.notifyObserversOf('loaded', context, function(err) { if (err) return next(err); next(null, context.data); }); }, function(err, results) { if (err) return cb(err); geoCallbackWithoutNotify(null, results); }); } var geoCallback = options.notify === false ? geoCallbackWithoutNotify : geoCallbackWithNotify; if (connector.all.length === 4) { connector.all(self.modelName, {}, options, geoCallback); } else { connector.all(self.modelName, {}, geoCallback); } } // already handled return cb.promise; } } var allCb = function(err, data) { if (!err && Array.isArray(data)) { async.map(data, function(item, next) { var Model = self.lookupModel(item); if (options.notify === false) { buildResult(item, next); } else { withNo ...
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } assert(arguments.length >= 1, 'The id argument is required'); if (options === undefined && cb === undefined) { if (typeof filter === 'function') { // findById(id, cb) cb = filter; filter = {}; } } else if (cb === undefined) { if (typeof options === 'function') { // findById(id, query, cb) cb = options; options = {}; if (typeof filter === 'object' && !(filter.include || filter.fields)) { // If filter doesn't have include or fields, assuming it's options options = filter; filter = {}; } } } cb = cb || utils.createPromiseCallback(); options = options || {}; filter = filter || {}; assert(typeof filter === 'object', 'The filter argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (isPKMissing(this, cb)) { return cb.promise; } else if (id == null || id === '') { process.nextTick(function() { cb(new Error(g.f('{{Model::findById}} requires the apidoc.element.loopback.DataSource.DataAccessObject.findById argument'))); }); } else { var query = byIdQuery(this, id); if (filter.include) { query.include = filter.include; } if (filter.fields) { query.fields = filter.fields; } this.findOne(query, options, cb); } return cb.promise; }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.DataSource.DataAccessObject.findById %s', id));
err.statusCode = 404;
...
findByIds = function (ids, query, options, cb) { if (options === undefined && cb === undefined) { if (typeof query === 'function') { // findByIds(ids, cb) cb = query; query = {}; } } else if (cb === undefined) { if (typeof options === 'function') { // findByIds(ids, query, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); options = options || {}; query = query || {}; assert(Array.isArray(ids), 'The ids argument must be an array'); assert(typeof query === 'object', 'The query argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (isPKMissing(this, cb)) { return cb.promise; } else if (ids.length === 0) { process.nextTick(function() { cb(null, []); }); return cb.promise; } var filter = {where: {}}; var pk = idName(this); filter.where[pk] = {inq: [].concat(ids)}; mergeQuery(filter, query || {}); // to know if the result need to be sorted by ids or not // this variable need to be initialized before the call to find, because filter is updated during the call with an order var toSortObjectsByIds = filter.order ? false : true; this.find(filter, options, function(err, results) { cb(err, toSortObjectsByIds ? utils.sortObjectsByIds(pk, ids, results) : results); }); return cb.promise; }
n/a
function findOne(query, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (options === undefined && cb === undefined) { if (typeof query === 'function') { cb = query; query = {}; } } else if (cb === undefined) { if (typeof options === 'function') { cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); query = query || {}; options = options || {}; assert(typeof query === 'object', 'The query argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); query.limit = 1; this.find(query, options, function(err, collection) { if (err || !collection || !collection.length > 0) return cb(err, null); cb(err, collection[0]); }); return cb.promise; }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } assert(arguments.length >= 1, 'At least one argument is required'); if (data === undefined && options === undefined && cb === undefined) { assert(typeof query === 'object', 'Single argument must be data object'); // findOrCreate(data); // query will be built from data, and method will return Promise data = query; query = {where: data}; } else if (options === undefined && cb === undefined) { if (typeof data === 'function') { // findOrCreate(data, cb); // query will be built from data cb = data; data = query; query = {where: data}; } } else if (cb === undefined) { if (typeof options === 'function') { // findOrCreate(query, data, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); query = query || {where: {}}; data = data || {}; options = options || {}; assert(typeof query === 'object', 'The query argument must be an object'); assert(typeof data === 'object', 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var hookState = {}; var Model = this; var self = this; var connector = Model.getConnector(); function _findOrCreate(query, data, currentInstance) { var modelName = self.modelName; function findOrCreateCallback(err, data, created) { if (err) return cb(err); var context = { Model: Model, data: data, isNewInstance: created, hookState: hookState, options: options, }; Model.notifyObserversOf('loaded', context, function(err) { if (err) return cb(err); var obj, Model = self.lookupModel(data); if (data) { obj = new Model(data, {fields: query.fields, applySetters: false, persisted: true}); } if (created) { var context = { Model: Model, instance: obj, isNewInstance: true, hookState: hookState, options: options, }; Model.notifyObserversOf('after save', context, function(err) { if (cb.promise) { cb(err, [obj, created]); } else { cb(err, obj, created); } }); } else { if (cb.promise) { cb(err, [obj, created]); } else { cb(err, obj, created); } } }); } data = removeUndefined(data); var context = { Model: Model, where: query.where, data: data, isNewInstance: true, currentInstance: currentInstance, hookState: hookState, options: options, }; Model.notifyObserversOf('persist', context, function(err) { if (err) return cb(err); if (connector.findOrCreate.length === 5) { connector.findOrCreate(modelName, query, self._forDB(context.data), options, findOrCreateCallback); } else { connector.findOrCreate(modelName, query, self._forDB(context.data), findOrCreateCallback); } }); } if (connector.findOrCreate) { query.limit = 1; try { this._normalize(query, options); } catch (err) { process.nextTick(function() { cb(err); }); return cb.promise; } this.applyScope(query); var context = { Model: Model, query: query, hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var query = ctx.query; var enforced = {}; var Model = self.lookupModel(data); var obj = data instanceof Model ? data : new Model(data); Model.applyProperties(enforced, obj); obj.setAttributes(enforced); var conte ...
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
getConnector = function () { return this.getDataSource().connector; }
n/a
function hasAndBelongsToMany(modelTo, params) { return RelationDefinition.hasAndBelongsToMany(this, modelTo, params); }
n/a
function hasMany(modelTo, params) { return RelationDefinition.hasMany(this, modelTo, params); }
n/a
function hasOne(modelTo, params) { return RelationDefinition.hasOne(this, modelTo, params); }
n/a
include = function (objects, include, options, cb) {
if (typeof options === 'function' && cb === undefined) {
cb = options;
options = {};
}
var self = this;
if (!include || (Array.isArray(include) && include.length === 0) ||
(Array.isArray(objects) && objects.length === 0) ||
(isPlainObject(include) && Object.keys(include).length === 0)) {
// The objects are empty
return process.nextTick(function() {
cb && cb(null, objects);
});
}
include = normalizeInclude(include);
// Find the limit of items for `inq`
var inqLimit = 256;
if (self.dataSource && self.dataSource.settings &&
self.dataSource.settings.inqLimit) {
inqLimit = self.dataSource.settings.inqLimit;
}
async.each(include, function(item, callback) {
processIncludeItem(objects, item, options, callback);
}, function(err) {
cb && cb(err, objects);
});
/**
* Find related items with an array of foreign keys by page
* @param model The model class
* @param filter The query filter
* @param fkName The name of the foreign key property
* @param pageSize The size of page
* @param options Options
* @param cb
*/
function findWithForeignKeysByPage(model, filter, fkName, pageSize, options, cb) {
var foreignKeys = [];
if (filter.where[fkName]) {
foreignKeys = filter.where[fkName].inq;
} else if (filter.where.and) {
// The inq can be embedded inside 'and: []'. No or: [] is needed as
// include only uses and. We only deal with the generated inq for include.
for (var j in filter.where.and) {
if (filter.where.and[j][fkName] &&
Array.isArray(filter.where.and[j][fkName].inq)) {
foreignKeys = filter.where.and[j][fkName].inq;
break;
}
}
}
if (!foreignKeys.length) {
return cb(null, []);
}
if (filter.limit || filter.skip || filter.offset) {
// Force the find to be performed per FK to honor the pagination
pageSize = 1;
}
var size = foreignKeys.length;
if (size > inqLimit && pageSize <= 0) {
pageSize = inqLimit;
}
if (pageSize <= 0) {
return model.find(filter, options, cb);
}
var listOfFKs = [];
for (var i = 0; i < size; i += pageSize) {
var end = i + pageSize;
if (end > size) {
end = size;
}
listOfFKs.push(foreignKeys.slice(i, end));
}
var items = [];
// Optimization: no need to resolve keys that are an empty array
listOfFKs = listOfFKs.filter(function(keys) {
return keys.length > 0;
});
async.each(listOfFKs, function(foreignKeys, done) {
var newFilter = {};
for (var f in filter) {
newFilter[f] = filter[f];
}
if (filter.where) {
newFilter.where = {};
for (var w in filter.where) {
newFilter.where[w] = filter.where[w];
}
}
newFilter.where[fkName] = {
inq: foreignKeys,
};
model.find(newFilter, options, function(err, results) {
if (err) return done(err);
items = items.concat(results);
done();
});
}, function(err) {
if (err) return cb(err);
cb(null, items);
});
}
function processIncludeItem(objs, include, options, cb) {
var relations = self.relations;
var relationName;
var subInclude = null, scope = null;
if (isPlainObject(include)) {
relationName = Object.keys(include)[0];
if (include[relationName] instanceof IncludeScope) {
scope = include[relationName];
subInclude = scope.include();
} else {
subInclude = include[relationName];
// when include = {user:true}, it does not have subInclude
if (subInclude === true) {
subInclude = null;
}
}
} else {
relationName = include;
subInclude = null;
}
var relation = relations[relationName];
if (!relation) {
cb(new Error(g.f('Relation "%s" is not defined for %s model', relationName, self.modelName)));
return;
}
var polymorphic = relation.p ...
n/a
lookupModel = function (data) { return this; }
n/a
function normalizeInclude(include) { var newInclude; if (typeof include === 'string') { return [include]; } else if (isPlainObject(include)) { // Build an array of key/value pairs newInclude = []; var rel = include.rel || include.relation; var obj = {}; if (typeof rel === 'string') { obj[rel] = new IncludeScope(include.scope); newInclude.push(obj); } else { for (var key in include) { obj[key] = include[key]; newInclude.push(obj); } } return newInclude; } else if (Array.isArray(include)) { newInclude = []; for (var i = 0, n = include.length; i < n; i++) { var subIncludes = normalizeInclude(include[i]); newInclude = newInclude.concat(subIncludes); } return newInclude; } else { return include; } }
n/a
patchOrCreate = function (data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (options === undefined && cb === undefined) { if (typeof data === 'function') { // upsert(cb) cb = data; data = {}; } } else if (cb === undefined) { if (typeof options === 'function') { // upsert(data, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); data = data || {}; options = options || {}; assert(typeof data === 'object', 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (Array.isArray(data)) { cb(new Error('updateOrCreate does not support bulk mode or any array input')); return cb.promise; } var hookState = {}; var self = this; var Model = this; var connector = Model.getConnector(); var id = getIdValue(this, data); if (id === undefined || id === null) { return this.create(data, options, cb); } var context = { Model: Model, query: byIdQuery(Model, id), hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, doUpdateOrCreate); function doUpdateOrCreate(err, ctx) { if (err) return cb(err); var isOriginalQuery = isWhereByGivenId(Model, ctx.query.where, id); if (connector.updateOrCreate && isOriginalQuery) { var context = { Model: Model, where: ctx.query.where, data: data, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); data = ctx.data; var update = data; var inst = data; if (!(data instanceof Model)) { inst = new Model(data, {applyDefaultValues: false}); } update = inst.toObject(false); Model.applyProperties(update, inst); Model = Model.lookupModel(update); var connector = self.getConnector(); var doValidate = undefined; if (options.validate === undefined) { if (Model.settings.validateUpsert === undefined) { if (Model.settings.automaticValidation !== undefined) { doValidate = Model.settings.automaticValidation; } } else { doValidate = Model.settings.validateUpsert; } } else { doValidate = options.validate; } if (doValidate === false) { callConnector(); } else { inst.isValid(function(valid) { if (!valid) { if (doValidate) { // backwards compatibility with validateUpsert:undefined return cb(new ValidationError(inst), inst); } else { // TODO(bajtos) Remove validateUpsert:undefined in v3.0 g.warn('Ignoring validation errors in {{updateOrCreate()}}:'); g.warn(' %s', new ValidationError(inst).message); // continue with updateOrCreate } } callConnector(); }, update, options); } function callConnector() { update = removeUndefined(update); context = { Model: Model, where: ctx.where, data: update, currentInstance: inst, hookState: ctx.hookState, options: options, }; Model.notifyObserversOf('persist', context, function(err) { if (err) return done(err); if (connector.updateOrCreate.length === 4) { connector.updateOrCreate(Model.modelName, update, options, done); } else { connector.updateOrCreate(Model.modelName, update, done); } }); } function done(err, data, info) { if (err) return cb(err); var context = { ...
n/a
patchOrCreateWithWhere = function (where, data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (cb === undefined) { if (typeof options === 'function') { // upsertWithWhere(where, data, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); options = options || {}; assert(typeof where === 'object', 'The where argument must be an object'); assert(typeof data === 'object', 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (Object.keys(data).length === 0) { var err = new Error('data object cannot be empty!'); err.statusCode = 400; process.nextTick(function() { cb(err); }); return cb.promise; } var hookState = {}; var self = this; var Model = this; var connector = Model.getConnector(); var modelName = Model.modelName; var query = {where: where}; var context = { Model: Model, query: query, hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, doUpsertWithWhere); function doUpsertWithWhere(err, ctx) { if (err) return cb(err); ctx.data = data; if (connector.upsertWithWhere) { var context = { Model: Model, where: ctx.query.where, data: ctx.data, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); data = ctx.data; var update = data; var inst = data; if (!(data instanceof Model)) { inst = new Model(data, {applyDefaultValues: false}); } update = inst.toObject(false); Model.applyScope(query); Model.applyProperties(update, inst); Model = Model.lookupModel(update); if (options.validate === false) { return callConnector(); } if (options.validate === undefined && Model.settings.automaticValidation === false) { return callConnector(); } inst.isValid(function(valid) { if (!valid) return cb(new ValidationError(inst), inst); callConnector(); }, update, options); function callConnector() { try { ctx.where = removeUndefined(ctx.where); ctx.where = Model._coerce(ctx.where, options); update = removeUndefined(update); update = Model._coerce(update, options); } catch (err) { return process.nextTick(function() { cb(err); }); } context = { Model: Model, where: ctx.where, data: update, currentInstance: inst, hookState: ctx.hookState, options: options, }; Model.notifyObserversOf('persist', context, function(err) { if (err) return done(err); connector.upsertWithWhere(modelName, ctx.where, update, options, done); }); } function done(err, data, info) { if (err) return cb(err); var contxt = { Model: Model, data: data, isNewInstance: info && info.isNewInstance, hookState: ctx.hookState, options: options, }; Model.notifyObserversOf('loaded', contxt, function(err) { if (err) return cb(err); var obj; if (contxt.data && !(contxt.data instanceof Model)) { inst._initProperties(contxt.data, {persisted: true}); obj = inst; } else { obj = contxt.data; } var context = { Model: Model, instance: obj, isNewInstance: info ? info.isNewInstance : undefined, hookState: hookState, options: options, }; ...
n/a
function referencesMany(modelTo, params) { return RelationDefinition.referencesMany(this, modelTo, params); }
n/a
function destroyAll(where, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } var Model = this; var connector = Model.getConnector(); assert(typeof connector.destroyAll === 'function', 'destroyAll() must be implemented by the connector'); if (options === undefined && cb === undefined) { if (typeof where === 'function') { cb = where; where = {}; } } else if (cb === undefined) { if (typeof options === 'function') { cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); where = where || {}; options = options || {}; assert(typeof where === 'object', 'The where argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var hookState = {}; var query = {where: where}; this.applyScope(query); where = query.where; if (options.notify === false) { doDelete(where); } else { query = {where: whereIsEmpty(where) ? {} : where}; var context = { Model: Model, query: query, hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var context = { Model: Model, where: ctx.query.where, hookState: hookState, options: options, }; Model.notifyObserversOf('before delete', context, function(err, ctx) { if (err) return cb(err); doDelete(ctx.where); }); }); } function doDelete(where) { var context = { Model: Model, where: whereIsEmpty(where) ? {} : where, hookState: hookState, options: options, }; if (whereIsEmpty(where)) { if (connector.destroyAll.length === 4) { connector.destroyAll(Model.modelName, {}, options, done); } else { connector.destroyAll(Model.modelName, {}, done); } } else { try { // Support an optional where object where = removeUndefined(where); where = Model._coerce(where, options); } catch (err) { return process.nextTick(function() { cb(err); }); } if (connector.destroyAll.length === 4) { connector.destroyAll(Model.modelName, where, options, done); } else { connector.destroyAll(Model.modelName, where, done); } } function done(err, info) { if (err) return cb(err); if (options.notify === false) { return cb(err, info); } var context = { Model: Model, where: where, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after delete', context, function(err) { cb(err, info); }); } } return cb.promise; }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
function deleteById(id, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } assert(arguments.length >= 1, 'The id argument is required'); if (cb === undefined) { if (typeof options === 'function') { // destroyById(id, cb) cb = options; options = {}; } } options = options || {}; cb = cb || utils.createPromiseCallback(); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (isPKMissing(this, cb)) { return cb.promise; } else if (id == null || id === '') { process.nextTick(function() { cb(new Error(g.f('{{Model::deleteById}} requires the apidoc.element.loopback.DataSource.DataAccessObject.removeById argument'))); }); return cb.promise; } var Model = this; this.remove(byIdQuery(this, id).where, options, function(err, info) { if (err) return cb(err); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { err = new Error(g.f('No instance with apidoc.element.loopback.DataSource.DataAccessObject.removeById %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err); } cb(null, info); }); return cb.promise; }
n/a
replaceById = function (id, data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (cb === undefined) { if (typeof options === 'function') { cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); options = options || {}; assert((typeof data === 'object') && (data !== null), 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var connector = this.getConnector(); var err; if (typeof connector.replaceById !== 'function') { err = new Error(g.f( 'The connector %s does not support {{replaceById}} operation. This is not a bug in LoopBack. ' + 'Please contact the authors of the connector, preferably via GitHub issues.', connector.name)); return cb(err); } var pkName = idName(this); if (!data[pkName]) data[pkName] = id; var Model = this; var inst = new Model(data, {persisted: true}); var enforced = {}; this.applyProperties(enforced, inst); inst.setAttributes(enforced); Model = this.lookupModel(data); // data-specific if (Model !== inst.constructor) inst = new Model(data); var strict = inst.__strict; if (isPKMissing(Model, cb)) return cb.promise; var model = Model.modelName; var hookState = {}; if (id !== data[pkName]) { err = new Error(g.f('apidoc.element.loopback.DataSource.DataAccessObject.replaceById property (%s) ' + 'cannot be updated from %s to %s', pkName, id, data[pkName])); err.statusCode = 400; process.nextTick(function() { cb(err); }); return cb.promise; } var context = { Model: Model, instance: inst, isNewInstance: false, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); if (ctx.instance[pkName] !== id && !Model._warned.cannotOverwritePKInBeforeSaveHook) { Model._warned.cannotOverwritePKInBeforeSaveHook = true; g.warn('WARNING: apidoc.element.loopback.DataSource.DataAccessObject.replaceById property cannot be changed from %s to %s for model:%s ' + 'in {{\'before save\'}} operation hook', id, inst[pkName], Model.modelName); } data = inst.toObject(false); if (strict) { applyStrictCheck(Model, strict, data, inst, validateAndCallConnector); } else { validateAndCallConnector(null, data); } function validateAndCallConnector(err, data) { if (err) return cb(err); data = removeUndefined(data); // update instance's properties inst.setAttributes(data); var doValidate = true; if (options.validate === undefined) { if (Model.settings.automaticValidation !== undefined) { doValidate = Model.settings.automaticValidation; } } else { doValidate = options.validate; } if (doValidate) { inst.isValid(function(valid) { if (!valid) return cb(new ValidationError(inst), inst); callConnector(); }, data, options); } else { callConnector(); } function callConnector() { copyData(data, inst); var typedData = convertSubsetOfPropertiesByType(inst, data); context.data = typedData; function replaceCallback(err, data) { if (err) return cb(err); var ctx = { Model: Model, hookState: hookState, data: context.data, isNewInstance: false, options: options, }; Model.notifyObserversOf('loaded', ctx, function(err) { if (err) return cb(err); if (ctx.data[pkName] !== id && !Model._warned.cannotOverwritePKInLoadedHook) { Model._warned.cannotOverwritePKInLoadedHook = true; g.warn('WARNING: apidoc.element.loopback.DataSource.DataAccessObject.replaceById property cannot be changed from %s to %s for model:%s in ' + '{{\'loaded\'}} operation hook', id, ctx.data[pkName], Model.modelName); ...
n/a
function replaceOrCreate(data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (cb === undefined) { if (typeof options === 'function') { // replaceOrCreta(data,cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); data = data || {}; options = options || {}; assert(typeof data === 'object', 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var hookState = {}; var self = this; var Model = this; var connector = Model.getConnector(); var id = getIdValue(this, data); if (id === undefined || id === null) { return this.create(data, options, cb); } var forceId = this.settings.forceId; if (forceId) { return Model.replaceById(id, data, options, cb); } var inst; if (data instanceof Model) { inst = data; } else { inst = new Model(data); } var strict = inst.__strict; var context = { Model: Model, query: byIdQuery(Model, id), hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, doReplaceOrCreate); function doReplaceOrCreate(err, ctx) { if (err) return cb(err); var isOriginalQuery = isWhereByGivenId(Model, ctx.query.where, id); var where = ctx.query.where; if (connector.replaceOrCreate && isOriginalQuery) { var context = { Model: Model, instance: inst, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); var update = inst.toObject(false); if (strict) { applyStrictCheck(Model, strict, update, inst, validateAndCallConnector); } else { validateAndCallConnector(); } function validateAndCallConnector(err) { if (err) return cb(err); Model.applyProperties(update, inst); Model = Model.lookupModel(update); var connector = self.getConnector(); if (options.validate === false) { return callConnector(); } // only when options.validate is not set, take model-setting into consideration if (options.validate === undefined && Model.settings.automaticValidation === false) { return callConnector(); } inst.isValid(function(valid) { if (!valid) return cb(new ValidationError(inst), inst); callConnector(); }, update, options); function callConnector() { update = removeUndefined(update); context = { Model: Model, where: where, data: update, currentInstance: inst, hookState: ctx.hookState, options: options, }; Model.notifyObserversOf('persist', context, function(err) { if (err) return done(err); connector.replaceOrCreate(Model.modelName, context.data, options, done); }); } function done(err, data, info) { if (err) return cb(err); var context = { Model: Model, data: data, isNewInstance: info ? info.isNewInstance : undefined, hookState: ctx.hookState, options: options, }; Model.notifyObserversOf('loaded', context, function(err) { if (err) return cb(err); var obj; if (data && !(data instanceof Model)) { inst._initProperties(data, {persisted: true}); obj = inst; } else { obj = data; } if (err) { cb(err, obj); } else { var context = { Model: Model, ...
n/a
scope = function (name, query, targetClass, methods, options) { var cls = this; if (options && options.isStatic === false) { cls = cls.prototype; } return defineScope(cls, targetClass || cls, name, query, methods, options); }
n/a
update = function (where, data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } assert(arguments.length >= 1, 'At least one argument is required'); if (data === undefined && options === undefined && cb === undefined && arguments.length === 1) { data = where; where = {}; } else if (options === undefined && cb === undefined) { // One of: // updateAll(data, cb) // updateAll(where, data) -> Promise if (typeof data === 'function') { cb = data; data = where; where = {}; } } else if (cb === undefined) { // One of: // updateAll(where, data, options) -> Promise // updateAll(where, data, cb) if (typeof options === 'function') { cb = options; options = {}; } } data = data || {}; options = options || {}; cb = cb || utils.createPromiseCallback(); assert(typeof where === 'object', 'The where argument must be an object'); assert(typeof data === 'object', 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var Model = this; var connector = Model.getDataSource().connector; assert(typeof connector.update === 'function', 'update() must be implemented by the connector'); var hookState = {}; var query = {where: where}; this.applyScope(query); this.applyProperties(data); where = query.where; var context = { Model: Model, query: {where: where}, hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var context = { Model: Model, where: ctx.query.where, data: data, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); doUpdate(ctx.where, ctx.data); }); }); function doUpdate(where, data) { try { where = removeUndefined(where); where = Model._coerce(where, options); data = removeUndefined(data); data = Model._coerce(data, options); } catch (err) { return process.nextTick(function() { cb(err); }); } function updateCallback(err, info) { if (err) return cb(err); var context = { Model: Model, where: where, data: data, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after save', context, function(err, ctx) { return cb(err, info); }); } var context = { Model: Model, where: where, data: data, hookState: hookState, options: options, }; Model.notifyObserversOf('persist', context, function(err, ctx) { if (err) return cb(err); if (connector.update.length === 5) { connector.update(Model.modelName, where, data, options, updateCallback); } else { connector.update(Model.modelName, where, data, updateCallback); } }); } return cb.promise; }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
updateAll = function (where, data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } assert(arguments.length >= 1, 'At least one argument is required'); if (data === undefined && options === undefined && cb === undefined && arguments.length === 1) { data = where; where = {}; } else if (options === undefined && cb === undefined) { // One of: // updateAll(data, cb) // updateAll(where, data) -> Promise if (typeof data === 'function') { cb = data; data = where; where = {}; } } else if (cb === undefined) { // One of: // updateAll(where, data, options) -> Promise // updateAll(where, data, cb) if (typeof options === 'function') { cb = options; options = {}; } } data = data || {}; options = options || {}; cb = cb || utils.createPromiseCallback(); assert(typeof where === 'object', 'The where argument must be an object'); assert(typeof data === 'object', 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var Model = this; var connector = Model.getDataSource().connector; assert(typeof connector.update === 'function', 'update() must be implemented by the connector'); var hookState = {}; var query = {where: where}; this.applyScope(query); this.applyProperties(data); where = query.where; var context = { Model: Model, query: {where: where}, hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var context = { Model: Model, where: ctx.query.where, data: data, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); doUpdate(ctx.where, ctx.data); }); }); function doUpdate(where, data) { try { where = removeUndefined(where); where = Model._coerce(where, options); data = removeUndefined(data); data = Model._coerce(data, options); } catch (err) { return process.nextTick(function() { cb(err); }); } function updateCallback(err, info) { if (err) return cb(err); var context = { Model: Model, where: where, data: data, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after save', context, function(err, ctx) { return cb(err, info); }); } var context = { Model: Model, where: where, data: data, hookState: hookState, options: options, }; Model.notifyObserversOf('persist', context, function(err, ctx) { if (err) return cb(err); if (connector.update.length === 5) { connector.update(Model.modelName, where, data, options, updateCallback); } else { connector.update(Model.modelName, where, data, updateCallback); } }); } return cb.promise; }
...
/**
* 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'}, ...}
...
updateOrCreate = function (data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (options === undefined && cb === undefined) { if (typeof data === 'function') { // upsert(cb) cb = data; data = {}; } } else if (cb === undefined) { if (typeof options === 'function') { // upsert(data, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); data = data || {}; options = options || {}; assert(typeof data === 'object', 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (Array.isArray(data)) { cb(new Error('updateOrCreate does not support bulk mode or any array input')); return cb.promise; } var hookState = {}; var self = this; var Model = this; var connector = Model.getConnector(); var id = getIdValue(this, data); if (id === undefined || id === null) { return this.create(data, options, cb); } var context = { Model: Model, query: byIdQuery(Model, id), hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, doUpdateOrCreate); function doUpdateOrCreate(err, ctx) { if (err) return cb(err); var isOriginalQuery = isWhereByGivenId(Model, ctx.query.where, id); if (connector.updateOrCreate && isOriginalQuery) { var context = { Model: Model, where: ctx.query.where, data: data, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); data = ctx.data; var update = data; var inst = data; if (!(data instanceof Model)) { inst = new Model(data, {applyDefaultValues: false}); } update = inst.toObject(false); Model.applyProperties(update, inst); Model = Model.lookupModel(update); var connector = self.getConnector(); var doValidate = undefined; if (options.validate === undefined) { if (Model.settings.validateUpsert === undefined) { if (Model.settings.automaticValidation !== undefined) { doValidate = Model.settings.automaticValidation; } } else { doValidate = Model.settings.validateUpsert; } } else { doValidate = options.validate; } if (doValidate === false) { callConnector(); } else { inst.isValid(function(valid) { if (!valid) { if (doValidate) { // backwards compatibility with validateUpsert:undefined return cb(new ValidationError(inst), inst); } else { // TODO(bajtos) Remove validateUpsert:undefined in v3.0 g.warn('Ignoring validation errors in {{updateOrCreate()}}:'); g.warn(' %s', new ValidationError(inst).message); // continue with updateOrCreate } } callConnector(); }, update, options); } function callConnector() { update = removeUndefined(update); context = { Model: Model, where: ctx.where, data: update, currentInstance: inst, hookState: ctx.hookState, options: options, }; Model.notifyObserversOf('persist', context, function(err) { if (err) return done(err); if (connector.updateOrCreate.length === 4) { connector.updateOrCreate(Model.modelName, update, options, done); } else { connector.updateOrCreate(Model.modelName, update, done); } }); } function done(err, data, info) { if (err) return cb(err); var context = { ...
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
upsert = function (data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (options === undefined && cb === undefined) { if (typeof data === 'function') { // upsert(cb) cb = data; data = {}; } } else if (cb === undefined) { if (typeof options === 'function') { // upsert(data, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); data = data || {}; options = options || {}; assert(typeof data === 'object', 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (Array.isArray(data)) { cb(new Error('updateOrCreate does not support bulk mode or any array input')); return cb.promise; } var hookState = {}; var self = this; var Model = this; var connector = Model.getConnector(); var id = getIdValue(this, data); if (id === undefined || id === null) { return this.create(data, options, cb); } var context = { Model: Model, query: byIdQuery(Model, id), hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, doUpdateOrCreate); function doUpdateOrCreate(err, ctx) { if (err) return cb(err); var isOriginalQuery = isWhereByGivenId(Model, ctx.query.where, id); if (connector.updateOrCreate && isOriginalQuery) { var context = { Model: Model, where: ctx.query.where, data: data, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); data = ctx.data; var update = data; var inst = data; if (!(data instanceof Model)) { inst = new Model(data, {applyDefaultValues: false}); } update = inst.toObject(false); Model.applyProperties(update, inst); Model = Model.lookupModel(update); var connector = self.getConnector(); var doValidate = undefined; if (options.validate === undefined) { if (Model.settings.validateUpsert === undefined) { if (Model.settings.automaticValidation !== undefined) { doValidate = Model.settings.automaticValidation; } } else { doValidate = Model.settings.validateUpsert; } } else { doValidate = options.validate; } if (doValidate === false) { callConnector(); } else { inst.isValid(function(valid) { if (!valid) { if (doValidate) { // backwards compatibility with validateUpsert:undefined return cb(new ValidationError(inst), inst); } else { // TODO(bajtos) Remove validateUpsert:undefined in v3.0 g.warn('Ignoring validation errors in {{updateOrCreate()}}:'); g.warn(' %s', new ValidationError(inst).message); // continue with updateOrCreate } } callConnector(); }, update, options); } function callConnector() { update = removeUndefined(update); context = { Model: Model, where: ctx.where, data: update, currentInstance: inst, hookState: ctx.hookState, options: options, }; Model.notifyObserversOf('persist', context, function(err) { if (err) return done(err); if (connector.updateOrCreate.length === 4) { connector.updateOrCreate(Model.modelName, update, options, done); } else { connector.updateOrCreate(Model.modelName, update, done); } }); } function done(err, data, info) { if (err) return cb(err); var context = { ...
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
upsertWithWhere = function (where, data, options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (cb === undefined) { if (typeof options === 'function') { // upsertWithWhere(where, data, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); options = options || {}; assert(typeof where === 'object', 'The where argument must be an object'); assert(typeof data === 'object', 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); if (Object.keys(data).length === 0) { var err = new Error('data object cannot be empty!'); err.statusCode = 400; process.nextTick(function() { cb(err); }); return cb.promise; } var hookState = {}; var self = this; var Model = this; var connector = Model.getConnector(); var modelName = Model.modelName; var query = {where: where}; var context = { Model: Model, query: query, hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, doUpsertWithWhere); function doUpsertWithWhere(err, ctx) { if (err) return cb(err); ctx.data = data; if (connector.upsertWithWhere) { var context = { Model: Model, where: ctx.query.where, data: ctx.data, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); data = ctx.data; var update = data; var inst = data; if (!(data instanceof Model)) { inst = new Model(data, {applyDefaultValues: false}); } update = inst.toObject(false); Model.applyScope(query); Model.applyProperties(update, inst); Model = Model.lookupModel(update); if (options.validate === false) { return callConnector(); } if (options.validate === undefined && Model.settings.automaticValidation === false) { return callConnector(); } inst.isValid(function(valid) { if (!valid) return cb(new ValidationError(inst), inst); callConnector(); }, update, options); function callConnector() { try { ctx.where = removeUndefined(ctx.where); ctx.where = Model._coerce(ctx.where, options); update = removeUndefined(update); update = Model._coerce(update, options); } catch (err) { return process.nextTick(function() { cb(err); }); } context = { Model: Model, where: ctx.where, data: update, currentInstance: inst, hookState: ctx.hookState, options: options, }; Model.notifyObserversOf('persist', context, function(err) { if (err) return done(err); connector.upsertWithWhere(modelName, ctx.where, update, options, done); }); } function done(err, data, info) { if (err) return cb(err); var contxt = { Model: Model, data: data, isNewInstance: info && info.isNewInstance, hookState: ctx.hookState, options: options, }; Model.notifyObserversOf('loaded', contxt, function(err) { if (err) return cb(err); var obj; if (contxt.data && !(contxt.data instanceof Model)) { inst._initProperties(contxt.data, {persisted: true}); obj = inst; } else { obj = contxt.data; } var context = { Model: Model, instance: obj, isNewInstance: info ? info.isNewInstance : undefined, hookState: hookState, options: options, }; ...
n/a
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
commit = function (cb) { var self = this; cb = cb || utils.createPromiseCallback(); // Report an error if the transaction is not active if (!self.connection) { process.nextTick(function() { cb(new Error(g.f('The {{transaction}} is not active: %s', self.id))); }); return cb.promise; } var context = { transaction: self, operation: 'commit', }; function work(done) { self.connector.commit(self.connection, done); } self.notifyObserversAround('commit', context, work, function(err) { // Deference the connection to mark the transaction is not active // The connection should have been released back the pool self.connection = null; cb(err); }); return cb.promise; }
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
rollback = function (cb) { var self = this; cb = cb || utils.createPromiseCallback(); // Report an error if the transaction is not active if (!self.connection) { process.nextTick(function() { cb(new Error(g.f('The {{transaction}} is not active: %s', self.id))); }); return cb.promise; } var context = { transaction: self, operation: 'rollback', }; function work(done) { self.connector.rollback(self.connection, done); } self.notifyObserversAround('rollback', context, work, function(err) { // Deference the connection to mark the transaction is not active // The connection should have been released back the pool self.connection = null; cb(err); }); return cb.promise; }
n/a
toJSON = function () { return this.id; }
...
if (err) {
if (done) done(err);
return;
}
mappings.forEach(function(m) {
var role;
if (options.returnOnlyRoleNames === true) {
role = m.toJSON().role.name;
} else {
role = m.roleId;
}
addRole(role);
});
if (done) done();
});
...
toString = function () { return this.id; }
...
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
if (err) return callback(err);
var modelIds = models.map(function(m) {
return m[idName].toString();
});
callback(null, changes.filter(function(ch) {
if (ch.type() === Change.DELETE) return true;
return modelIds.indexOf(ch.modelId) > -1;
}));
});
});
...
delete = function (options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (cb === undefined && typeof options === 'function') { cb = options; options = {}; } cb = cb || utils.createPromiseCallback(); options = options || {}; assert(typeof options === 'object', 'The options argument should be an object'); assert(typeof cb === 'function', 'The cb argument should be a function'); var inst = this; var connector = this.getConnector(); var Model = this.constructor; var id = getIdValue(this.constructor, this); var hookState = {}; if (isPKMissing(Model, cb)) return cb.promise; var context = { Model: Model, query: byIdQuery(Model, id), hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var context = { Model: Model, where: ctx.query.where, instance: inst, hookState: hookState, options: options, }; Model.notifyObserversOf('before delete', context, function(err, ctx) { if (err) return cb(err); doDeleteInstance(ctx.where); }); }); function doDeleteInstance(where) { if (!isWhereByGivenId(Model, where, id)) { // A hook modified the query, it is no longer // a simple 'delete model with the given id'. // We must switch to full query-based delete. Model.deleteAll(where, {notify: false}, function(err, info) { if (err) return cb(err, false); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { err = new Error(g.f('No instance with apidoc.element.loopback.DataSource.DataAccessObject.prototype.delete %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err, false); } var context = { Model: Model, where: where, instance: inst, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after delete', context, function(err) { cb(err, info); }); }); return; } inst.trigger('destroy', function(destroyed) { function destroyCallback(err, info) { if (err) return cb(err); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { err = new Error(g.f('No instance with apidoc.element.loopback.DataSource.DataAccessObject.prototype.delete %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err); } destroyed(function() { var context = { Model: Model, where: where, instance: inst, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after delete', context, function(err) { cb(err, info); }); }); } if (connector.destroy.length === 4) { connector.destroy(inst.constructor.modelName, id, options, destroyCallback); } else { connector.destroy(inst.constructor.modelName, id, destroyCallback); } }, null, cb); } return cb.promise; }
n/a
destroy = function (options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (cb === undefined && typeof options === 'function') { cb = options; options = {}; } cb = cb || utils.createPromiseCallback(); options = options || {}; assert(typeof options === 'object', 'The options argument should be an object'); assert(typeof cb === 'function', 'The cb argument should be a function'); var inst = this; var connector = this.getConnector(); var Model = this.constructor; var id = getIdValue(this.constructor, this); var hookState = {}; if (isPKMissing(Model, cb)) return cb.promise; var context = { Model: Model, query: byIdQuery(Model, id), hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var context = { Model: Model, where: ctx.query.where, instance: inst, hookState: hookState, options: options, }; Model.notifyObserversOf('before delete', context, function(err, ctx) { if (err) return cb(err); doDeleteInstance(ctx.where); }); }); function doDeleteInstance(where) { if (!isWhereByGivenId(Model, where, id)) { // A hook modified the query, it is no longer // a simple 'delete model with the given id'. // We must switch to full query-based delete. Model.deleteAll(where, {notify: false}, function(err, info) { if (err) return cb(err, false); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { err = new Error(g.f('No instance with apidoc.element.loopback.DataSource.DataAccessObject.prototype.destroy %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err, false); } var context = { Model: Model, where: where, instance: inst, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after delete', context, function(err) { cb(err, info); }); }); return; } inst.trigger('destroy', function(destroyed) { function destroyCallback(err, info) { if (err) return cb(err); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { err = new Error(g.f('No instance with apidoc.element.loopback.DataSource.DataAccessObject.prototype.destroy %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err); } destroyed(function() { var context = { Model: Model, where: where, instance: inst, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after delete', context, function(err) { cb(err, info); }); }); } if (connector.destroy.length === 4) { connector.destroy(inst.constructor.modelName, id, options, destroyCallback); } else { connector.destroy(inst.constructor.modelName, id, destroyCallback); } }, null, cb); } return cb.promise; }
...
elapsedSeconds < secondsToLive;
if (isValid) {
process.nextTick(function() {
cb(null, isValid);
});
} else {
this.destroy(function(err) {
cb(err, isValid);
});
}
} catch (e) {
process.nextTick(function() {
cb(e);
});
...
getConnector = function () { return this.getDataSource().connector; }
n/a
isNewRecord = function () { return !this.__persisted; }
n/a
patchAttributes = function (data, options, cb) { var self = this; var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (options === undefined && cb === undefined) { if (typeof data === 'function') { // updateAttributes(cb) cb = data; data = undefined; } } else if (cb === undefined) { if (typeof options === 'function') { // updateAttributes(data, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); options = options || {}; assert((typeof data === 'object') && (data !== null), 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var inst = this; var Model = this.constructor; var connector = inst.getConnector(); assert(typeof connector.updateAttributes === 'function', 'updateAttributes() must be implemented by the connector'); if (isPKMissing(Model, cb)) return cb.promise; var allowExtendedOperators = Model._allowExtendedOperators(options); var strict = this.__strict; var model = Model.modelName; var hookState = {}; // Convert the data to be plain object so that update won't be confused if (data instanceof Model) { data = data.toObject(false); } data = removeUndefined(data); // Make sure id(s) cannot be changed var idNames = Model.definition.idNames(); for (var i = 0, n = idNames.length; i < n; i++) { var idName = idNames[i]; if (data[idName] !== undefined && !idEquals(data[idName], inst[idName])) { var err = new Error(g.f('apidoc.element.loopback.DataSource.DataAccessObject.prototype.patchAttributes cannot be updated from ' + '%s to %s when {{forceId}} is set to true', inst[idName], data[idName])); err.statusCode = 400; process.nextTick(function() { cb(err); }); return cb.promise; } } var context = { Model: Model, where: byIdQuery(Model, getIdValue(Model, inst)).where, data: data, currentInstance: inst, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); data = ctx.data; if (strict && !allowExtendedOperators) { applyStrictCheck(self.constructor, strict, data, inst, validateAndSave); } else { validateAndSave(null, data); } function validateAndSave(err, data) { if (err) return cb(err); data = removeUndefined(data); var doValidate = true; if (options.validate === undefined) { if (Model.settings.automaticValidation !== undefined) { doValidate = Model.settings.automaticValidation; } } else { doValidate = options.validate; } // update instance's properties try { inst.setAttributes(data); } catch (err) { return cb(err); } if (doValidate) { inst.isValid(function(valid) { if (!valid) { cb(new ValidationError(inst), inst); return; } triggerSave(); }, data, options); } else { triggerSave(); } function triggerSave() { inst.trigger('save', function(saveDone) { inst.trigger('update', function(done) { copyData(data, inst); var typedData = convertSubsetOfPropertiesByType(inst, data); context.data = typedData; function updateAttributesCallback(err) { if (err) return cb(err); var ctx = { Model: Model, data: context.data, hookState: hookState, options: options, isNewInstance: false, }; Model.notifyObserversOf('loaded', ctx, function(err) { if (err) return cb(err); inst.__persisted = true; // By default, the instance passed to updateAttributes callback is NOT updated ...
...
try {
User.validatePassword(newPassword);
} catch (err) {
return cb(err);
}
const delta = {password: newPassword};
this.patchAttributes(delta, options, (err, updated) => cb(err));
});
return cb.promise;
};
/**
* Verify a user's identity by sending them a confirmation email.
*
...
function reload(cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } return this.constructor.findById(getIdValue(this.constructor, this), cb); }
n/a
remove = function (options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (cb === undefined && typeof options === 'function') { cb = options; options = {}; } cb = cb || utils.createPromiseCallback(); options = options || {}; assert(typeof options === 'object', 'The options argument should be an object'); assert(typeof cb === 'function', 'The cb argument should be a function'); var inst = this; var connector = this.getConnector(); var Model = this.constructor; var id = getIdValue(this.constructor, this); var hookState = {}; if (isPKMissing(Model, cb)) return cb.promise; var context = { Model: Model, query: byIdQuery(Model, id), hookState: hookState, options: options, }; Model.notifyObserversOf('access', context, function(err, ctx) { if (err) return cb(err); var context = { Model: Model, where: ctx.query.where, instance: inst, hookState: hookState, options: options, }; Model.notifyObserversOf('before delete', context, function(err, ctx) { if (err) return cb(err); doDeleteInstance(ctx.where); }); }); function doDeleteInstance(where) { if (!isWhereByGivenId(Model, where, id)) { // A hook modified the query, it is no longer // a simple 'delete model with the given id'. // We must switch to full query-based delete. Model.deleteAll(where, {notify: false}, function(err, info) { if (err) return cb(err, false); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { err = new Error(g.f('No instance with apidoc.element.loopback.DataSource.DataAccessObject.prototype.remove %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err, false); } var context = { Model: Model, where: where, instance: inst, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after delete', context, function(err) { cb(err, info); }); }); return; } inst.trigger('destroy', function(destroyed) { function destroyCallback(err, info) { if (err) return cb(err); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { err = new Error(g.f('No instance with apidoc.element.loopback.DataSource.DataAccessObject.prototype.remove %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err); } destroyed(function() { var context = { Model: Model, where: where, instance: inst, hookState: hookState, options: options, info: info, }; Model.notifyObserversOf('after delete', context, function(err) { cb(err, info); }); }); } if (connector.destroy.length === 4) { connector.destroy(inst.constructor.modelName, id, options, destroyCallback); } else { connector.destroy(inst.constructor.modelName, id, destroyCallback); } }, null, cb); } return cb.promise; }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
replaceAttributes = function (data, options, cb) { var Model = this.constructor; var id = getIdValue(this.constructor, this); return Model.replaceById(id, data, options, cb); }
n/a
save = function (options, cb) { var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } var Model = this.constructor; if (typeof options === 'function') { cb = options; options = {}; } cb = cb || utils.createPromiseCallback(); options = options || {}; assert(typeof options === 'object', 'The options argument should be an object'); assert(typeof cb === 'function', 'The cb argument should be a function'); if (isPKMissing(Model, cb)) { return cb.promise; } else if (this.isNewRecord()) { return Model.create(this, options, cb); } var hookState = {}; if (options.validate === undefined) { if (Model.settings.automaticValidation === undefined) { options.validate = true; } else { options.validate = Model.settings.automaticValidation; } } if (options.throws === undefined) { options.throws = false; } var inst = this; var connector = inst.getConnector(); var modelName = Model.modelName; var context = { Model: Model, instance: inst, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err) { if (err) return cb(err); var data = inst.toObject(true); Model.applyProperties(data, inst); inst.setAttributes(data); // validate first if (!options.validate) { return save(); } inst.isValid(function(valid) { if (valid) { save(); } else { var err = new ValidationError(inst); // throws option is dangerous for async usage if (options.throws) { throw err; } cb(err, inst); } }, data, options); // then save function save() { inst.trigger('save', function(saveDone) { inst.trigger('update', function(updateDone) { data = removeUndefined(data); function saveCallback(err, unusedData, result) { if (err) { return cb(err, inst); } var context = { Model: Model, data: data, isNewInstance: result && result.isNewInstance, hookState: hookState, options: options, }; Model.notifyObserversOf('loaded', context, function(err) { if (err) return cb(err); inst._initProperties(data, {persisted: true}); var context = { Model: Model, instance: inst, isNewInstance: result && result.isNewInstance, hookState: hookState, options: options, }; Model.notifyObserversOf('after save', context, function(err) { if (err) return cb(err, inst); updateDone.call(inst, function() { saveDone.call(inst, function() { cb(err, inst); }); }); }); }); } context = { Model: Model, data: data, where: byIdQuery(Model, getIdValue(Model, inst)).where, currentInstance: inst, hookState: hookState, options: options, }; Model.notifyObserversOf('persist', context, function(err) { if (err) return cb(err); if (connector.save.length === 4) { connector.save(modelName, inst.constructor._forDB(data), options, saveCallback); } else { connector.save(modelName, inst.constructor._forDB(data), saveCallback); } }); }, data, cb); }, data, cb); } }); return cb.promise; }
...
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
* Get a change's current revision based on current data.
* @callback {Function} callback
...
function setAttribute(name, value) { this[name] = value; // TODO [fabien] - currently not protected by applyProperties }
n/a
function setAttributes(data) { if (typeof data !== 'object') return; this.constructor.applyProperties(data, this); var Model = this.constructor; var inst = this; // update instance's properties for (var key in data) { inst.setAttribute(key, data[key]); } Model.emit('set', inst); }
...
if (!data) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
conflict.models(function(err, source, target) {
if (err) return done(err);
var inst = source || new conflict.SourceModel(target);
inst.setAttributes(data);
inst.save(function(err) {
if (err) return done(err);
conflict.resolve(done);
});
});
function done(err) {
...
function unsetAttribute(name, nullify) { if (nullify || this.constructor.definition.settings.persistUndefinedAsNull) { this[name] = this.__data[name] = null; } else { delete this[name]; delete this.__data[name]; } }
n/a
function updateAttribute(name, value, options, cb) { var data = {}; data[name] = value; return this.updateAttributes(data, options, cb); }
n/a
updateAttributes = function (data, options, cb) { var self = this; var connectionPromise = stillConnecting(this.getDataSource(), this, arguments); if (connectionPromise) { return connectionPromise; } if (options === undefined && cb === undefined) { if (typeof data === 'function') { // updateAttributes(cb) cb = data; data = undefined; } } else if (cb === undefined) { if (typeof options === 'function') { // updateAttributes(data, cb) cb = options; options = {}; } } cb = cb || utils.createPromiseCallback(); options = options || {}; assert((typeof data === 'object') && (data !== null), 'The data argument must be an object'); assert(typeof options === 'object', 'The options argument must be an object'); assert(typeof cb === 'function', 'The cb argument must be a function'); var inst = this; var Model = this.constructor; var connector = inst.getConnector(); assert(typeof connector.updateAttributes === 'function', 'updateAttributes() must be implemented by the connector'); if (isPKMissing(Model, cb)) return cb.promise; var allowExtendedOperators = Model._allowExtendedOperators(options); var strict = this.__strict; var model = Model.modelName; var hookState = {}; // Convert the data to be plain object so that update won't be confused if (data instanceof Model) { data = data.toObject(false); } data = removeUndefined(data); // Make sure id(s) cannot be changed var idNames = Model.definition.idNames(); for (var i = 0, n = idNames.length; i < n; i++) { var idName = idNames[i]; if (data[idName] !== undefined && !idEquals(data[idName], inst[idName])) { var err = new Error(g.f('apidoc.element.loopback.DataSource.DataAccessObject.prototype.updateAttributes cannot be updated from ' + '%s to %s when {{forceId}} is set to true', inst[idName], data[idName])); err.statusCode = 400; process.nextTick(function() { cb(err); }); return cb.promise; } } var context = { Model: Model, where: byIdQuery(Model, getIdValue(Model, inst)).where, data: data, currentInstance: inst, hookState: hookState, options: options, }; Model.notifyObserversOf('before save', context, function(err, ctx) { if (err) return cb(err); data = ctx.data; if (strict && !allowExtendedOperators) { applyStrictCheck(self.constructor, strict, data, inst, validateAndSave); } else { validateAndSave(null, data); } function validateAndSave(err, data) { if (err) return cb(err); data = removeUndefined(data); var doValidate = true; if (options.validate === undefined) { if (Model.settings.automaticValidation !== undefined) { doValidate = Model.settings.automaticValidation; } } else { doValidate = options.validate; } // update instance's properties try { inst.setAttributes(data); } catch (err) { return cb(err); } if (doValidate) { inst.isValid(function(valid) { if (!valid) { cb(new ValidationError(inst), inst); return; } triggerSave(); }, data, options); } else { triggerSave(); } function triggerSave() { inst.trigger('save', function(saveDone) { inst.trigger('update', function(done) { copyData(data, inst); var typedData = convertSubsetOfPropertiesByType(inst, data); context.data = typedData; function updateAttributesCallback(err) { if (err) return cb(err); var ctx = { Model: Model, data: context.data, hookState: hookState, options: options, isNewInstance: false, }; Model.notifyObserversOf('loaded', ctx, function(err) { if (err) return cb(err); inst.__persisted = true; // By default, the instance passed to updateAttributes callback is NOT updated ...
...
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
inst.updateAttributes(data, cb);
});
};
/**
* Create a change stream. [See here for more info](http://loopback.io/doc/en/lb2/Realtime-server-sent-events.html)
*
* @param {Object} options
...
function JSON(value) { if (!(this instanceof JSON)) { return value; } this.value = value; }
n/a
toJSON = function () { return this.value; }
...
if (err) {
if (done) done(err);
return;
}
mappings.forEach(function(m) {
var role;
if (options.returnOnlyRoleNames === true) {
role = m.toJSON().role.name;
} else {
role = m.roleId;
}
addRole(role);
});
if (done) done();
});
...
toObject = function () { return this.value; }
...
options.validate = true;
}
if (!('throws' in options)) {
options.throws = false;
}
var inst = this;
var data = inst.toObject(true);
var id = this.getId();
if (!id) {
return Model.create(this, callback);
}
// validate first
...
function Text(value) { if (!(this instanceof Text)) { return value; } this.value = value; }
n/a
toJSON = function () { return this.value; }
...
if (err) {
if (done) done(err);
return;
}
mappings.forEach(function(m) {
var role;
if (options.returnOnlyRoleNames === true) {
role = m.toJSON().role.name;
} else {
role = m.roleId;
}
addRole(role);
});
if (done) done();
});
...
toObject = function () { return this.value; }
...
options.validate = true;
}
if (!('throws' in options)) {
options.throws = false;
}
var inst = this;
var data = inst.toObject(true);
var id = this.getId();
if (!id) {
return Model.create(this, callback);
}
// validate first
...
_setupConnector = function () { this.connector = this.connector || this.adapter; // The legacy JugglingDB adapter will set up `adapter` property this.adapter = this.connector; // Keep the adapter as an alias to connector if (this.connector) { if (!this.connector.dataSource) { // Set up the dataSource if the connector doesn't do so this.connector.dataSource = this; } var dataSource = this; this.connector.log = function(query, start) { dataSource.log(query, start); }; this.connector.logger = function(query) { var t1 = Date.now(); var log = this.log; return function(q) { log(q || query, t1); }; }; // Configure the connector instance to mix in observer functions jutil.mixin(this.connector, OberserverMixin); } }
n/a
attach = function (modelClass) { if (modelClass.dataSource === this) { // Already attached to the data source return modelClass; } if (modelClass.modelBuilder !== this.modelBuilder) { this.modelBuilder.definitions[modelClass.modelName] = modelClass.definition; this.modelBuilder.models[modelClass.modelName] = modelClass; // reset the modelBuilder modelClass.modelBuilder = this.modelBuilder; } // redefine the dataSource modelClass.dataSource = this; this.setupDataAccess(modelClass, modelClass.settings); modelClass.emit('dataSourceAttached', modelClass); return modelClass; }
n/a
automigrate = function (models, cb) { this.freeze(); if ((!cb) && ('function' === typeof models)) { cb = models; models = undefined; } cb = cb || utils.createPromiseCallback(); if (!this.connector.automigrate) { // NOOP process.nextTick(cb); return cb.promise; } // First argument is a model name if ('string' === typeof models) { models = [models]; } var attachedModels = this.connector._models; if (attachedModels && typeof attachedModels === 'object') { models = models || Object.keys(attachedModels); if (models.length === 0) { process.nextTick(cb); return cb.promise; } var invalidModels = models.filter(function(m) { return !(m in attachedModels); }); if (invalidModels.length) { process.nextTick(function() { cb(new Error(g.f('Cannot migrate models not attached to this datasource: %s', invalidModels.join(' ')))); }); return cb.promise; } } this.connector.automigrate(models, cb); return cb.promise; }
n/a
autoupdate = function (models, cb) { this.freeze(); if ((!cb) && ('function' === typeof models)) { cb = models; models = undefined; } cb = cb || utils.createPromiseCallback(); if (!this.connector.autoupdate) { // NOOP process.nextTick(cb); return cb.promise; } // First argument is a model name if ('string' === typeof models) { models = [models]; } var attachedModels = this.connector._models; if (attachedModels && typeof attachedModels === 'object') { models = models || Object.keys(attachedModels); if (models.length === 0) { process.nextTick(cb); return cb.promise; } var invalidModels = models.filter(function(m) { return !(m in attachedModels); }); if (invalidModels.length) { process.nextTick(function() { cb(new Error(g.f('Cannot migrate models not attached to this datasource: %s', invalidModels.join(' ')))); }); return cb.promise; } } this.connector.autoupdate(models, cb); return cb.promise; }
n/a
buildModelFromInstance = function (name, json, options) { // Introspect the JSON document to generate a schema var schema = ModelBuilder.introspect(json); // Create a model for the generated schema return this.createModel(name, schema, options); }
n/a
columnMetadata = function (modelName, propertyName) { return this.getModelDefinition(modelName).columnMetadata(this.connector.name, propertyName); }
n/a
columnName = function (modelName, propertyName) { return this.getModelDefinition(modelName).columnName(this.connector.name, propertyName); }
n/a
columnNames = function (modelName) { return this.getModelDefinition(modelName).columnNames(this.connector.name); }
n/a
connect = function (callback) { callback = callback || utils.createPromiseCallback(); var self = this; if (this.connected) { // The data source is already connected, return immediately process.nextTick(callback); return callback.promise; } if (typeof this.connector.connect !== 'function') { // Connector doesn't have the connect function // Assume no connect is needed self.connected = true; self.connecting = false; process.nextTick(function() { self.emit('connected'); callback(); }); return callback.promise; } // Queue the callback this.pendingConnectCallbacks = this.pendingConnectCallbacks || []; this.pendingConnectCallbacks.push(callback); // The connect is already in progress if (this.connecting) return callback.promise; // Set connecting flag to be true this.connecting = true; this.connector.connect(function(err, result) { self.connecting = false; if (!err) self.connected = true; var cbs = self.pendingConnectCallbacks; self.pendingConnectCallbacks = []; if (!err) { self.emit('connected'); } else { self.emit('error', err); } // Invoke all pending callbacks async.each(cbs, function(cb, done) { try { cb(err); } catch (e) { // Ignore error to make sure all callbacks are invoked debug('Uncaught error raised by connect callback function: ', e); } finally { done(); } }, function(err) { if (err) throw err; // It should not happen }); }); return callback.promise; }
n/a
function copyModel(Master) { var dataSource = this; var className = Master.modelName; var md = Master.modelBuilder.getModelDefinition(className); var Slave = function SlaveModel() { Master.apply(this, [].slice.call(arguments)); }; util.inherits(Slave, Master); // Delegating static properties Slave.__proto__ = Master; hiddenProperty(Slave, 'dataSource', dataSource); hiddenProperty(Slave, 'modelName', className); hiddenProperty(Slave, 'relations', Master.relations); if (!(className in dataSource.modelBuilder.models)) { // store class in model pool dataSource.modelBuilder.models[className] = Slave; dataSource.modelBuilder.definitions[className] = new ModelDefinition(dataSource.modelBuilder, md.name, md.properties, md.settings); if ((!dataSource.isTransaction) && dataSource.connector && dataSource.connector.define) { dataSource.connector.define({ model: Slave, properties: md.properties, settings: md.settings, }); } } return Slave; }
n/a
function defineClass(className, properties, settings) { var args = slice.call(arguments); if (!className) { throw new Error(g.f('Class name required')); } if (args.length === 1) { properties = {}; args.push(properties); } if (args.length === 2) { settings = {}; args.push(settings); } properties = properties || {}; settings = settings || {}; if (this.isRelational()) { // Set the strict mode to be true for relational DBs by default if (settings.strict === undefined || settings.strict === null) { settings.strict = true; } if (settings.strict === false) { settings.strict = 'throw'; } } var modelClass = this.modelBuilder.define(className, properties, settings); modelClass.dataSource = this; if (settings.unresolved) { return modelClass; } this.setupDataAccess(modelClass, settings); modelClass.emit('dataSourceAttached', modelClass); return modelClass; }
...
app.model = function(Model, config) {
var isPublic = true;
var registry = this.registry;
if (typeof Model === 'string') {
var msg = 'app.model(modelName, settings) is no longer supported. ' +
'Use app.registry.createModel(modelName, definition) and ' +
'app.model(ModelCtor, config) instead.';
throw new Error(msg);
}
if (arguments.length > 1) {
config = config || {};
configureModel(Model, config, this);
...
function defineClass(className, properties, settings) { var args = slice.call(arguments); if (!className) { throw new Error(g.f('Class name required')); } if (args.length === 1) { properties = {}; args.push(properties); } if (args.length === 2) { settings = {}; args.push(settings); } properties = properties || {}; settings = settings || {}; if (this.isRelational()) { // Set the strict mode to be true for relational DBs by default if (settings.strict === undefined || settings.strict === null) { settings.strict = true; } if (settings.strict === false) { settings.strict = 'throw'; } } var modelClass = this.modelBuilder.define(className, properties, settings); modelClass.dataSource = this; if (settings.unresolved) { return modelClass; } this.setupDataAccess(modelClass, settings); modelClass.emit('dataSourceAttached', modelClass); return modelClass; }
...
* @property {DataSource} Model.dataSource Data source to which the model is connected, if any. Static property.
* @property {SharedClass} Model.sharedMethod The `strong-remoting` [SharedClass](http://apidocs.strongloop.com/strong-remoting/#
sharedclass) that contains remoting (and http) metadata. Static property.
* @property {Object} settings Contains additional model settings.
* @property {string} settings.http.path Base URL of the model HTTP route.
* @property [{string}] settings.acls Array of ACLs for the model.
* @class
*/
var Model = registry.modelBuilder.define('Model');
Model.registry = registry;
/**
* The `loopback.Model.extend()` method calls this when you create a model that extends another model.
* Add any setup or configuration code you want executed when the model is created.
* See [Setting up a custom model](http://loopback.io/doc/en/lb2/Extending-built-in-models.html#setting-up-a-custom-model).
...
function defineForeignKey(className, key, foreignClassName, pkName) { var pkType = null; var foreignModel = this.getModelDefinition(foreignClassName); pkName = pkName || foreignModel && foreignModel.idName(); if (pkName) { pkType = foreignModel.properties[pkName].type; } var model = this.getModelDefinition(className); if (model.properties[key]) { if (pkType) { // Reset the type of the foreign key model.rawProperties[key].type = model.properties[key].type = pkType; } return; } var fkDef = {type: pkType}; var foreignMeta = this.columnMetadata(foreignClassName, pkName); if (foreignMeta && (foreignMeta.dataType || foreignMeta.dataLength)) { fkDef[this.connector.name] = {}; if (foreignMeta.dataType) { fkDef[this.connector.name].dataType = foreignMeta.dataType; } if (foreignMeta.dataLength) { fkDef[this.connector.name].dataLength = foreignMeta.dataLength; } } if (this.connector.defineForeignKey) { var cb = function(err, keyType) { if (err) throw err; fkDef.type = keyType || pkType; // Add the foreign key property to the data source _models this.defineProperty(className, key, fkDef); }.bind(this); switch (this.connector.defineForeignKey.length) { case 4: this.connector.defineForeignKey(className, key, foreignClassName, cb); break; default: case 3: this.connector.defineForeignKey(className, key, cb); break; } } else { // Add the foreign key property to the data source _models this.defineProperty(className, key, fkDef); } }
n/a
defineOperation = function (name, options, fn) { options.fn = fn; options.name = name; this._operations[name] = options; }
n/a
defineProperty = function (model, prop, params) { this.modelBuilder.defineProperty(model, prop, params); var resolvedProp = this.getModelDefinition(model).properties[prop]; if (this.connector && this.connector.defineProperty) { this.connector.defineProperty(model, prop, resolvedProp); } }
n/a
defineRelations = function (modelClass, relations) { var self = this; // Create a function for the closure in the loop var createListener = function(name, relation, targetModel, throughModel) { if (!isModelDataSourceAttached(targetModel)) { targetModel.once('dataAccessConfigured', function(model) { // Check if the through model doesn't exist or resolved if (!throughModel || isModelDataSourceAttached(throughModel)) { // The target model is resolved var params = traverse(relation).clone(); params.as = name; params.model = model; if (throughModel) { params.through = throughModel; } modelClass[relation.type].call(modelClass, name, params); } }); } if (throughModel && !isModelDataSourceAttached(throughModel)) { // Set up a listener to the through model throughModel.once('dataAccessConfigured', function(model) { if (isModelDataSourceAttached(targetModel)) { // The target model is resolved var params = traverse(relation).clone(); params.as = name; params.model = targetModel; params.through = model; modelClass[relation.type].call(modelClass, name, params); } }); } }; // Set up the relations if (relations) { Object.keys(relations).forEach(function(rn) { var r = relations[rn]; assert(DataSource.relationTypes.indexOf(r.type) !== -1, 'Invalid relation type: ' + r.type); assert(isValidRelationName(rn), 'Invalid relation name: ' + rn); var targetModel, polymorphicName; if (r.polymorphic && r.type !== 'belongsTo' && !r.model) { throw new Error(g.f('No model specified for {{polymorphic}} %s: %s', r.type, rn)); } if (r.polymorphic) { polymorphicName = typeof r.model === 'string' ? r.model : rn; if (typeof r.polymorphic === 'string') { polymorphicName = r.polymorphic; } else if (typeof r.polymorphic === 'object' && typeof r.polymorphic.as === 'string') { polymorphicName = r.polymorphic.as; } } if (r.model) { targetModel = isModelClass(r.model) ? r.model : self.getModel(r.model, true); } var throughModel = null; if (r.through) { throughModel = isModelClass(r.through) ? r.through : self.getModel(r.through, true); } if ((targetModel && !isModelDataSourceAttached(targetModel)) || (throughModel && !isModelDataSourceAttached(throughModel))) { // Create a listener to defer the relation set up createListener(rn, r, targetModel, throughModel); } else { // The target model is resolved var params = traverse(r).clone(); params.as = rn; params.model = polymorphicName || targetModel; if (throughModel) { params.through = throughModel; } modelClass[r.type].call(modelClass, rn, params); } }); } }
n/a
defineScopes = function (modelClass, scopes) { if (scopes) { for (var s in scopes) { defineScope(modelClass, modelClass, s, scopes[s], {}, scopes[s].options); } } }
n/a
disableRemote = function (operation) { var op = this.getOperation(operation); if (op) { op.remoteEnabled = false; } else { throw new Error(g.f('%s is not provided by the attached connector', operation)); } }
n/a
function disconnect(cb) { var self = this; if (this.connected && (typeof this.connector.disconnect === 'function')) { this.connector.disconnect(function(err, result) { self.connected = false; cb && cb(err, result); }); } else { process.nextTick(function() { self.connected = false; cb && cb(); }); } }
n/a
discoverAndBuildModels = function (modelName, options, cb) { var self = this; options = options || {}; this.discoverSchemas(modelName, options, function(err, schemas) { if (err) { cb && cb(err, schemas); return; } var schemaList = []; for (var s in schemas) { var schema = schemas[s]; if (options.base) { schema.options = schema.options || {}; schema.options.base = options.base; } schemaList.push(schema); } var models = self.modelBuilder.buildModels(schemaList, self.createModel.bind(self)); cb && cb(err, models); }); }
n/a
discoverAndBuildModelsSync = function (modelName, options) { options = options || {}; var schemas = this.discoverSchemasSync(modelName, options); var schemaList = []; for (var s in schemas) { var schema = schemas[s]; if (options.base) { schema.options = schema.options || {}; schema.options.base = options.base; } schemaList.push(schema); } var models = this.modelBuilder.buildModels(schemaList, this.createModel.bind(this)); return models; }
n/a
discoverExportedForeignKeys = function (modelName, options, cb) { this.freeze(); if (cb === undefined && typeof options === 'function') { cb = options; options = {}; } options = options || {}; cb = cb || utils.createPromiseCallback(); if (this.connector.discoverExportedForeignKeys) { this.connector.discoverExportedForeignKeys(modelName, options, cb); } else if (cb) { process.nextTick(cb); } return cb.promise; }
n/a
discoverExportedForeignKeysSync = function (modelName, options) { this.freeze(); if (this.connector.discoverExportedForeignKeysSync) { return this.connector.discoverExportedForeignKeysSync(modelName, options); } return null; }
n/a
discoverForeignKeys = function (modelName, options, cb) { this.freeze(); if (cb === undefined && typeof options === 'function') { cb = options; options = {}; } options = options || {}; cb = cb || utils.createPromiseCallback(); if (this.connector.discoverForeignKeys) { this.connector.discoverForeignKeys(modelName, options, cb); } else if (cb) { process.nextTick(cb); } return cb.promise; }
n/a
discoverForeignKeysSync = function (modelName, options) { this.freeze(); if (this.connector.discoverForeignKeysSync) { return this.connector.discoverForeignKeysSync(modelName, options); } return null; }
n/a
discoverModelDefinitions = function (options, cb) { this.freeze(); if (cb === undefined && typeof options === 'function') { cb = options; options = {}; } options = options || {}; cb = cb || utils.createPromiseCallback(); if (this.connector.discoverModelDefinitions) { this.connector.discoverModelDefinitions(options, cb); } else if (cb) { process.nextTick(cb); } return cb.promise; }
n/a
discoverModelDefinitionsSync = function (options) { this.freeze(); if (this.connector.discoverModelDefinitionsSync) { return this.connector.discoverModelDefinitionsSync(options); } return null; }
n/a
discoverModelProperties = function (modelName, options, cb) { this.freeze(); if (cb === undefined && typeof options === 'function') { cb = options; options = {}; } options = options || {}; cb = cb || utils.createPromiseCallback(); if (this.connector.discoverModelProperties) { this.connector.discoverModelProperties(modelName, options, cb); } else if (cb) { process.nextTick(cb); } return cb.promise; }
n/a
discoverModelPropertiesSync = function (modelName, options) { this.freeze(); if (this.connector.discoverModelPropertiesSync) { return this.connector.discoverModelPropertiesSync(modelName, options); } return null; }
n/a
discoverPrimaryKeys = function (modelName, options, cb) { this.freeze(); if (cb === undefined && typeof options === 'function') { cb = options; options = {}; } options = options || {}; cb = cb || utils.createPromiseCallback(); if (this.connector.discoverPrimaryKeys) { this.connector.discoverPrimaryKeys(modelName, options, cb); } else if (cb) { process.nextTick(cb); } return cb.promise; }
n/a
discoverPrimaryKeysSync = function (modelName, options) { this.freeze(); if (this.connector.discoverPrimaryKeysSync) { return this.connector.discoverPrimaryKeysSync(modelName, options); } return null; }
n/a
discoverSchema = function (modelName, options, cb) { options = options || {}; if (!cb && 'function' === typeof options) { cb = options; options = {}; } options.visited = {}; options.relations = false; cb = cb || utils.createPromiseCallback(); this.discoverSchemas(modelName, options, function(err, schemas) { if (err || !schemas) { cb && cb(err, schemas); return; } for (var s in schemas) { cb && cb(null, schemas[s]); return; } }); return cb.promise; }
n/a
discoverSchemas = function (modelName, options, cb) { options = options || {}; if (!cb && 'function' === typeof options) { cb = options; options = {}; } cb = cb || utils.createPromiseCallback(); var self = this; var dbType = this.connector.name || this.name; var nameMapper; if (options.nameMapper === null) { // No mapping nameMapper = function(type, name) { return name; }; } else if (typeof options.nameMapper === 'function') { // Custom name mapper nameMapper = options.nameMapper; } else { // Default name mapper nameMapper = function mapName(type, name) { if (type === 'table' || type === 'model') { return fromDBName(name, false); } else if (type == 'fk') { return fromDBName(name + 'Rel', true); } else { return fromDBName(name, true); } }; } if (this.connector.discoverSchemas) { // Delegate to the connector implementation this.connector.discoverSchemas(modelName, options, cb); return cb.promise; } var tasks = [ this.discoverModelProperties.bind(this, modelName, options), this.discoverPrimaryKeys.bind(this, modelName, options)]; var followingRelations = options.associations || options.relations; if (followingRelations) { tasks.push(this.discoverForeignKeys.bind(this, modelName, options)); } async.parallel(tasks, function(err, results) { if (err) { cb(err); return cb.promise; } var columns = results[0]; if (!columns || columns.length === 0) { cb(new Error(g.f('Table \'%s\' does not exist.', modelName))); return cb.promise; } // Handle primary keys var primaryKeys = results[1] || []; var pks = {}; primaryKeys.forEach(function(pk) { pks[pk.columnName] = pk.keySeq; }); if (self.settings.debug) { debug('Primary keys: ', pks); } var schema = { name: nameMapper('table', modelName), options: { idInjection: false, // DO NOT add id property }, properties: {}, }; schema.options[dbType] = { schema: columns[0].owner, table: modelName, }; columns.forEach(function(item) { var propName = nameMapper('column', item.columnName); schema.properties[propName] = { type: item.type, required: (item.nullable === 'N' || item.nullable === 'NO' || item.nullable === 0 || item.nullable === false), length: item.dataLength, precision: item.dataPrecision, scale: item.dataScale, }; if (pks[item.columnName]) { schema.properties[propName].id = pks[item.columnName]; } var dbSpecific = schema.properties[propName][dbType] = { columnName: item.columnName, dataType: item.dataType, dataLength: item.dataLength, dataPrecision: item.dataPrecision, dataScale: item.dataScale, nullable: item.nullable, }; // merge connector-specific properties if (item[dbType]) { for (var k in item[dbType]) { dbSpecific[k] = item[dbType][k]; } } }); // Add current modelName to the visited tables options.visited = options.visited || {}; var schemaKey = columns[0].owner + '.' + modelName; if (!options.visited.hasOwnProperty(schemaKey)) { if (self.settings.debug) { debug('Adding schema for ' + schemaKey); } options.visited[schemaKey] = schema; } var otherTables = {}; if (followingRelations) { // Handle foreign keys var fks = {}; var foreignKeys = results[2] || []; foreignKeys.forEach(function(fk) { var fkInfo = { keySeq: fk.keySeq, owner: fk.pkOwner, tableName: fk.pkTableName, columnName: fk.pkColumnName, }; if (fks[fk.fkName]) { fks[fk.fkName].push(fkInfo); } else { fks[fk.fkName] = [fkInfo]; } }); if (self.settings.debug) { debug('Foreign keys: ', fks); } schema.options.relations = {}; fo ...
n/a
discoverSchemasSync = function (modelName, options) { var self = this; var dbType = this.name || this.connector.name; var columns = this.discoverModelPropertiesSync(modelName, options); if (!columns || columns.length === 0) { return []; } var nameMapper = options.nameMapper || function mapName(type, name) { if (type === 'table' || type === 'model') { return fromDBName(name, false); } else { return fromDBName(name, true); } }; // Handle primary keys var primaryKeys = this.discoverPrimaryKeysSync(modelName, options); var pks = {}; primaryKeys.forEach(function(pk) { pks[pk.columnName] = pk.keySeq; }); if (self.settings.debug) { debug('Primary keys: ', pks); } var schema = { name: nameMapper('table', modelName), options: { idInjection: false, // DO NOT add id property }, properties: {}, }; schema.options[dbType] = { schema: columns.length > 0 && columns[0].owner, table: modelName, }; columns.forEach(function(item) { var i = item; var propName = nameMapper('column', item.columnName); schema.properties[propName] = { type: item.type, required: (item.nullable === 'N'), length: item.dataLength, precision: item.dataPrecision, scale: item.dataScale, }; if (pks[item.columnName]) { schema.properties[propName].id = pks[item.columnName]; } schema.properties[propName][dbType] = { columnName: i.columnName, dataType: i.dataType, dataLength: i.dataLength, dataPrecision: item.dataPrecision, dataScale: item.dataScale, nullable: i.nullable, }; }); // Add current modelName to the visited tables options.visited = options.visited || {}; var schemaKey = columns[0].owner + '.' + modelName; if (!options.visited.hasOwnProperty(schemaKey)) { if (self.settings.debug) { debug('Adding schema for ' + schemaKey); } options.visited[schemaKey] = schema; } var otherTables = {}; var followingRelations = options.associations || options.relations; if (followingRelations) { // Handle foreign keys var fks = {}; var foreignKeys = this.discoverForeignKeysSync(modelName, options); foreignKeys.forEach(function(fk) { var fkInfo = { keySeq: fk.keySeq, owner: fk.pkOwner, tableName: fk.pkTableName, columnName: fk.pkColumnName, }; if (fks[fk.fkName]) { fks[fk.fkName].push(fkInfo); } else { fks[fk.fkName] = [fkInfo]; } }); if (self.settings.debug) { debug('Foreign keys: ', fks); } schema.options.relations = {}; foreignKeys.forEach(function(fk) { var propName = nameMapper('column', fk.pkTableName); schema.options.relations[propName] = { model: nameMapper('table', fk.pkTableName), type: 'belongsTo', foreignKey: nameMapper('column', fk.fkColumnName), }; var key = fk.pkOwner + '.' + fk.pkTableName; if (!options.visited.hasOwnProperty(key) && !otherTables.hasOwnProperty(key)) { otherTables[key] = {owner: fk.pkOwner, tableName: fk.pkTableName}; } }); } if (Object.keys(otherTables).length === 0) { return options.visited; } else { var moreTasks = []; for (var t in otherTables) { if (self.settings.debug) { debug('Discovering related schema for ' + schemaKey); } var newOptions = {}; for (var key in options) { newOptions[key] = options[key]; } newOptions.owner = otherTables[t].owner; self.discoverSchemasSync(otherTables[t].tableName, newOptions); } return options.visited; } }
n/a
enableRemote = function (operation) { var op = this.getOperation(operation); if (op) { op.remoteEnabled = true; } else { throw new Error(g.f('%s is not provided by the attached connector', operation)); } }
n/a
function freeze() { if (!this.connector) { throw new Error(g.f('The connector has not been initialized.')); } if (this.connector.freezeDataSource) { this.connector.freezeDataSource(); } if (this.connector.freezeSchema) { this.connector.freezeSchema(); } }
n/a
getModel = function (name, forceCreate) { return this.modelBuilder.getModel(name, forceCreate); }
...
context = context || {};
assert(context.registry,
'Application registry is mandatory in AccessContext but missing in provided context');
this.registry = context.registry;
this.principals = context.principals || [];
var model = context.model;
model = ('string' === typeof model) ? this.registry.getModel(model) : model
;
this.model = model;
this.modelName = model && model.modelName;
this.modelId = context.id || context.modelId;
this.property = context.property || AccessContext.ALL;
this.method = context.method;
...
getModelDefinition = function (name) { return this.modelBuilder.getModelDefinition(name); }
n/a
getOperation = function (operation) { var ops = this.operations(); var opKeys = Object.keys(ops); for (var i = 0; i < opKeys.length; i++) { var op = ops[opKeys[i]]; if (op.name === operation) { return op; } } }
n/a
getTypes = function () { var getTypes = this.connector && this.connector.getTypes; var types = getTypes && getTypes() || []; if (typeof types === 'string') { types = types.split(/[\s,\/]+/); } return types; }
...
var ds = new DataSource(name, options, self.modelBuilder);
ds.createModel = function(name, properties, settings) {
settings = settings || {};
var BaseModel = settings.base || settings.super;
if (!BaseModel) {
// Check the connector types
var connectorTypes = ds.getTypes();
if (Array.isArray(connectorTypes) && connectorTypes.indexOf('db') !== -1) {
// Only set up the base model to PersistedModel if the connector is DB
BaseModel = self.PersistedModel;
} else {
BaseModel = self.Model;
}
settings.base = BaseModel;
...
idColumnName = function (modelName) { return this.getModelDefinition(modelName).idColumnName(this.connector.name); }
n/a
idName = function (modelName) { if (!this.getModelDefinition(modelName).idName) { g.error('No apidoc.element.loopback.DataSource.prototype.idName name %s', this.getModelDefinition(modelName)); } return this.getModelDefinition(modelName).idName(); }
...
*/
PersistedModel.getIdName = function() {
var Model = this;
var ds = Model.getDataSource();
if (ds.idName) {
return ds.idName(Model.modelName);
} else {
return 'id';
}
};
PersistedModel.setupRemoting = function() {
var PersistedModel = this;
...
idNames = function (modelName) { return this.getModelDefinition(modelName).idNames(); }
n/a
idProperty = function (modelName) { var def = this.getModelDefinition(modelName); var idProps = def && def.ids(); return idProps && idProps[0] && idProps[0].property; }
n/a
isActual = function (models, cb) { this.freeze(); if (this.connector.isActual) { this.connector.isActual(models, cb); } else { if ((!cb) && ('function' === typeof models)) { cb = models; models = undefined; } if (cb) { process.nextTick(function() { cb(null, true); }); } } }
n/a
isRelational = function () { return this.connector && this.connector.relational; }
n/a
log = function (sql, t) { debug(sql, t); this.emit('log', sql, t); }
...
app.listen(0, function() {
process.env.PORT = this.address().port;
done();
});
});
grunt.registerTask('skip-karma-on-windows', function() {
console.log('*** SKIPPING PHANTOM-JS BASED TESTS ON WINDOWS ***');
});
grunt.registerTask('e2e', ['e2e-server', 'karma:e2e']);
// Default task.
grunt.registerTask('default', ['browserify']);
...
mixin = function (ModelCtor) { var ops = this.operations(); var DAO = this.DataAccessObject; // mixin DAO jutil.mixin(ModelCtor, DAO, {proxyFunctions: true, override: true}); // decorate operations as alias functions Object.keys(ops).forEach(function(name) { var op = ops[name]; var scope; if (op.enabled) { scope = op.prototype ? ModelCtor.prototype : ModelCtor; // var sfn = scope[name] = function () { // op.scope[op.fnName].apply(self, arguments); // } Object.keys(op) .filter(function(key) { // filter out the following keys return ~[ 'scope', 'fnName', 'prototype', ].indexOf(key); }) .forEach(function(key) { if (typeof op[key] !== 'undefined') { op.scope[op.fnName][key] = op[key]; } }); } }); }
n/a
operations = function () { return this._operations; }
n/a
ping = function (cb) { var self = this; if (self.connector.ping) { this.connector.ping(cb); } else if (self.connector.discoverModelProperties) { self.discoverModelProperties('dummy', {}, cb); } else { process.nextTick(function() { var err = self.connected ? null : new Error(g.f('Not connected')); cb(err); }); } }
n/a
ready = function (obj, args) { var self = this; if (this.connected) { // Connected return false; } var method = args.callee; // Set up a callback after the connection is established to continue the method call var onConnected = null, onError = null, timeoutHandle = null; onConnected = function() { // Remove the error handler self.removeListener('error', onError); if (timeoutHandle) { clearTimeout(timeoutHandle); } var params = [].slice.call(args); try { method.apply(obj, params); } catch (err) { // Catch the exception and report it via callback var cb = params.pop(); if (typeof cb === 'function') { process.nextTick(function() { cb(err); }); } else { throw err; } } }; onError = function(err) { // Remove the connected listener self.removeListener('connected', onConnected); if (timeoutHandle) { clearTimeout(timeoutHandle); } var params = [].slice.call(args); var cb = params.pop(); if (typeof cb === 'function') { process.nextTick(function() { cb(err); }); } }; this.once('connected', onConnected); this.once('error', onError); // Set up a timeout to cancel the invocation var timeout = this.settings.connectionTimeout || 5000; timeoutHandle = setTimeout(function() { self.removeListener('error', onError); self.removeListener('connected', onConnected); var params = [].slice.call(args); var cb = params.pop(); if (typeof cb === 'function') { cb(new Error(g.f('Timeout in connecting after %s ms', timeout))); } }, timeout); if (!this.connecting) { this.connect(); } return true; }
n/a
setup = function (name, settings) { var dataSource = this; var connector; // support single settings object if (name && typeof name === 'object' && !settings) { settings = name; name = undefined; } if (typeof settings === 'object') { if (settings.initialize) { connector = settings; } else if (settings.connector) { connector = settings.connector; } else if (settings.adapter) { connector = settings.adapter; } } // just save everything we get this.settings = settings || {}; this.settings.debug = this.settings.debug || debug.enabled; if (this.settings.debug) { debug('Settings: %j', this.settings); } // Disconnected by default this.connected = false; this.connecting = false; if (typeof connector === 'string') { name = connector; connector = undefined; } name = name || (connector && connector.name); this.name = name; if (name && !connector) { if (typeof name === 'object') { // The first argument might be the connector itself connector = name; this.name = connector.name; } else { // The connector has not been resolved var result = DataSource._resolveConnector(name); connector = result.connector; if (!connector) { console.error(result.error); this.emit('error', new Error(result.error)); return; } } } if (connector) { var postInit = function postInit(err, result) { this._setupConnector(); // we have an connector now? if (!this.connector) { throw new Error(g.f('Connector is not defined correctly: ' + 'it should create `{{connector}}` member of dataSource')); } this.connected = !err; // Connected now if (this.connected) { this.emit('connected'); } else { // The connection fails, let's report it and hope it will be recovered in the next call g.error('Connection fails: %s\nIt will be retried for the next request.', err); this.emit('error', err); this.connecting = false; } }.bind(this); try { if ('function' === typeof connector.initialize) { // Call the async initialize method connector.initialize(this, postInit); } else if ('function' === typeof connector) { // Use the connector constructor directly this.connector = new connector(this.settings); postInit(); } } catch (err) { if (err.message) { err.message = 'Cannot initialize connector ' + JSON.stringify(connector.name || name) + ': ' + err.message; } throw err; } } }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupDataAccess = function (modelClass, settings) { if (this.connector) { // Check if the id property should be generated var idName = modelClass.definition.idName(); var idProp = modelClass.definition.rawProperties[idName]; if (idProp && idProp.generated && this.connector.getDefaultIdType) { // Set the default id type from connector's ability var idType = this.connector.getDefaultIdType() || String; idProp.type = idType; modelClass.definition.rawProperties[idName].type = idType; modelClass.definition.properties[idName].type = idType; var forceId = settings.forceId; if (idProp.generated && forceId !== false) { forceId = true; } if (forceId) { modelClass.validatesAbsenceOf(idName, {if: 'isNewRecord'}); } } if (this.connector.define) { // pass control to connector this.connector.define({ model: modelClass, properties: modelClass.definition.properties, settings: settings, }); } } // add data access objects this.mixin(modelClass); // define relations from LDL (options.relations) var relations = settings.relationships || settings.relations; this.defineRelations(modelClass, relations); // Emit the dataAccessConfigured event to indicate all the methods for data // access have been mixed into the model class modelClass.emit('dataAccessConfigured', modelClass); // define scopes from LDL (options.relations) var scopes = settings.scopes || {}; this.defineScopes(modelClass, scopes); }
n/a
supportTypes = function (types) { var supportedTypes = this.getTypes(); if (Array.isArray(types)) { // Check each of the types for (var i = 0; i < types.length; i++) { if (supportedTypes.indexOf(types[i]) === -1) { // Not supported return false; } } return true; } else { // The types is a string return supportedTypes.indexOf(types) !== -1; } }
n/a
tableName = function (modelName) { return this.getModelDefinition(modelName).tableName(this.connector.name); }
n/a
transaction = function () { var dataSource = this; var transaction = new EventEmitter(); for (var p in dataSource) { transaction[p] = dataSource[p]; } transaction.isTransaction = true; transaction.origin = dataSource; transaction.name = dataSource.name; transaction.settings = dataSource.settings; transaction.connected = false; transaction.connecting = false; transaction.connector = dataSource.connector.transaction(); // create blank models pool transaction.modelBuilder = new ModelBuilder(); transaction.models = transaction.modelBuilder.models; transaction.definitions = transaction.modelBuilder.definitions; for (var i in dataSource.modelBuilder.models) { dataSource.copyModel.call(transaction, dataSource.modelBuilder.models[i]); } transaction.exec = function(cb) { transaction.connector.exec(cb); }; return transaction; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
eventNames = function () { [native code] }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
send = function () { throw new Error(g.f('You must connect the {{Email}} Model to a {{Mail}} connector')); }
...
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
* @header var app = loopback()
...
setMaxListeners = function () { [native code] }
n/a
setup = function () { var ModelCtor = this; var Parent = this.super_; if (!ModelCtor.registry && Parent && Parent.registry) { ModelCtor.registry = Parent.registry; } var options = this.settings; var typeName = this.modelName; // support remoting prototype methods // it's important to setup this function *before* calling `new SharedClass` // otherwise remoting metadata from our base model is picked up ModelCtor.sharedCtor = function(data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.Email.setup %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.Email.setup or {{data}}'))); } }; var idDesc = ModelCtor.modelName + ' id'; ModelCtor.sharedCtor.accepts = [ {arg: 'id', type: 'any', required: true, http: {source: 'path'}, description: idDesc}, // {arg: 'instance', type: 'object', http: {source: 'body'}} {arg: 'options', type: 'object', http: createOptionsViaModelMethod}, ]; ModelCtor.sharedCtor.http = [ {path: '/:id'}, ]; ModelCtor.sharedCtor.returns = {root: true}; var remotingOptions = {}; extend(remotingOptions, options.remoting || {}); // create a sharedClass var sharedClass = ModelCtor.sharedClass = new SharedClass( ModelCtor.modelName, ModelCtor, remotingOptions ); // before remote hook ModelCtor.beforeRemote = function(name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }; // after remote hook ModelCtor.afterRemote = function(name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }; ModelCtor.afterRemoteError = function(name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }; ModelCtor._runWhenAttachedToApp = function(fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }; if ('injectOptionsFromRemoteContext' in options) { console.warn(g.f( '%s is using model setting %s which is no longer available.', typeName, 'injectOptionsFromRemoteContext')); console.warn(g.f( 'Please rework your app to use the offical solution for injecting ' + '"options" argument from request context,\nsee %s', 'http://loopback.io/doc/en/lb3/Using-current-context.html')); } // resolve relation functions sharedClass.resolve(function resolver(d ...
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.Email.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.Email.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
send = function () { throw new Error(g.f('You must connect the {{Email}} Model to a {{Mail}} connector')); }
...
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
* @header var app = loopback()
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
[Model undefined]
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
eventNames = function () { [native code] }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
setup = function () { var ModelCtor = this; var Parent = this.super_; if (!ModelCtor.registry && Parent && Parent.registry) { ModelCtor.registry = Parent.registry; } var options = this.settings; var typeName = this.modelName; // support remoting prototype methods // it's important to setup this function *before* calling `new SharedClass` // otherwise remoting metadata from our base model is picked up ModelCtor.sharedCtor = function(data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.Email.super_.setup %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.Email.super_.setup or {{data}}'))); } }; var idDesc = ModelCtor.modelName + ' id'; ModelCtor.sharedCtor.accepts = [ {arg: 'id', type: 'any', required: true, http: {source: 'path'}, description: idDesc}, // {arg: 'instance', type: 'object', http: {source: 'body'}} {arg: 'options', type: 'object', http: createOptionsViaModelMethod}, ]; ModelCtor.sharedCtor.http = [ {path: '/:id'}, ]; ModelCtor.sharedCtor.returns = {root: true}; var remotingOptions = {}; extend(remotingOptions, options.remoting || {}); // create a sharedClass var sharedClass = ModelCtor.sharedClass = new SharedClass( ModelCtor.modelName, ModelCtor, remotingOptions ); // before remote hook ModelCtor.beforeRemote = function(name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }; // after remote hook ModelCtor.afterRemote = function(name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }; ModelCtor.afterRemoteError = function(name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }; ModelCtor._runWhenAttachedToApp = function(fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }; if ('injectOptionsFromRemoteContext' in options) { console.warn(g.f( '%s is using model setting %s which is no longer available.', typeName, 'injectOptionsFromRemoteContext')); console.warn(g.f( 'Please rework your app to use the offical solution for injecting ' + '"options" argument from request context,\nsee %s', 'http://loopback.io/doc/en/lb3/Using-current-context.html')); } // resolve relation functions sharedClass.resolve(function resolver(d ...
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.Email.super_.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.Email.super_.sharedCtor or {{data}}'))); } }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
_initProperties = function (data, options) {
var self = this;
var ctor = this.constructor;
// issue#1261
if (typeof data !== 'undefined' && data.constructor &&
typeof (data.constructor) !== 'function') {
throw new Error(g.f('Property name "function Object() { [native code] }" is not allowed in %s data', ctor.modelName));
}
if (data instanceof ctor) {
// Convert the data to be plain object to avoid pollutions
data = data.toObject(false);
}
var properties = _extend({}, ctor.definition.properties);
data = data || {};
if (typeof ctor.applyProperties === 'function') {
ctor.applyProperties(data);
}
options = options || {};
var applySetters = options.applySetters;
var applyDefaultValues = options.applyDefaultValues;
var strict = options.strict;
if (strict === undefined) {
strict = ctor.definition.settings.strict;
} else if (strict === 'throw') {
g.warn('Warning: Model %s, {{strict mode: `throw`}} has been removed, ' +
'please use {{`strict: true`}} instead, which returns' +
'{{`Validation Error`}} for unknown properties,', ctor.modelName);
}
var persistUndefinedAsNull = ctor.definition.settings.persistUndefinedAsNull;
if (ctor.hideInternalProperties) {
// Object.defineProperty() is expensive. We only try to make the internal
// properties hidden (non-enumerable) if the model class has the
// `hideInternalProperties` set to true
Object.defineProperties(this, {
__cachedRelations: {
writable: true,
enumerable: false,
configurable: true,
value: {},
},
__data: {
writable: true,
enumerable: false,
configurable: true,
value: {},
},
// Instance level data source
__dataSource: {
writable: true,
enumerable: false,
configurable: true,
value: options.dataSource,
},
// Instance level strict mode
__strict: {
writable: true,
enumerable: false,
configurable: true,
value: strict,
},
__persisted: {
writable: true,
enumerable: false,
configurable: true,
value: false,
},
});
if (strict) {
Object.defineProperty(this, '__unknownProperties', {
writable: true,
enumerable: false,
configrable: true,
value: [],
});
}
} else {
this.__cachedRelations = {};
this.__data = {};
this.__dataSource = options.dataSource;
this.__strict = strict;
this.__persisted = false;
if (strict) {
this.__unknownProperties = [];
}
}
if (options.persisted !== undefined) {
this.__persisted = options.persisted === true;
}
if (data.__cachedRelations) {
this.__cachedRelations = data.__cachedRelations;
}
var keys = Object.keys(data);
if (Array.isArray(options.fields)) {
keys = keys.filter(function(k) {
return (options.fields.indexOf(k) != -1);
});
}
var size = keys.length;
var p, propVal;
for (var k = 0; k < size; k++) {
p = keys[k];
propVal = data[p];
if (typeof propVal === 'function') {
continue;
}
if (propVal === undefined && persistUndefinedAsNull) {
propVal = null;
}
if (properties[p]) {
// Managed property
if (applySetters || properties[p].id) {
self[p] = propVal;
} else {
self.__data[p] = propVal;
}
} else if (ctor.relations[p]) {
var relationType = ctor.relations[p].type;
var modelTo;
if (!properties[p]) {
modelTo = ctor.relations[p].modelTo || ModelBaseClass;
var multiple = ctor.relations[p].multiple;
var typeName = multiple ? 'Array' : modelTo.modelName;
var propType = multiple ? [modelTo] : modelTo;
properties[p] = {name: typeName, type: propType};
/* Issue #1252
this.setStrict(false);
*/
}
// Relation
if (relationType === 'belongsTo' && propVal != null) {
// If the related model is populated
self.__data ...
...
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
}, data);
...
fromObject = function (obj) { for (var key in obj) { this[key] = obj[key]; } }
n/a
getDataSource = function () { return this.__dataSource || this.constructor.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getPropertyType = function (propName) { return this.constructor.getPropertyType(propName); }
n/a
inspect = function (depth) { if (INSPECT_SUPPORTS_OBJECT_RETVAL) return this.__data; // Workaround for older versions // See also https://github.com/joyent/node/commit/66280de133 return util.inspect(this.__data, { showHidden: false, depth: depth, colors: false, }); }
n/a
isValid = function (callback, data, options) { options = options || {}; var valid = true, inst = this, wait = 0, async = false; var validations = this.constructor.validations; var reportDiscardedProperties = this.__strict && this.__unknownProperties && this.__unknownProperties.length; // exit with success when no errors if (typeof validations !== 'object' && !reportDiscardedProperties) { cleanErrors(this); if (callback) { this.trigger('validate', function(validationsDone) { validationsDone.call(inst, function() { callback(valid); }); }, data, callback); } return valid; } Object.defineProperty(this, 'errors', { enumerable: false, configurable: true, value: new Errors, }); this.trigger('validate', function(validationsDone) { var inst = this, asyncFail = false; var attrs = Object.keys(validations || {}); attrs.forEach(function(attr) { var attrValidations = validations[attr] || []; attrValidations.forEach(function(v) { if (v.options && v.options.async) { async = true; wait += 1; process.nextTick(function() { validationFailed(inst, attr, v, options, done); }); } else { if (validationFailed(inst, attr, v)) { valid = false; } } }); }); if (reportDiscardedProperties) { for (var ix in inst.__unknownProperties) { var key = inst.__unknownProperties[ix]; var code = 'unknown-property'; var msg = defaultMessages[code]; inst.errors.add(key, msg, code); valid = false; } } if (!async) { validationsDone.call(inst, function() { if (valid) cleanErrors(inst); if (callback) { callback(valid); } }); } function done(fail) { asyncFail = asyncFail || fail; if (--wait === 0) { validationsDone.call(inst, function() { if (valid && !asyncFail) cleanErrors(inst); if (callback) { callback(valid && !asyncFail); } }); } } }, data, callback); if (async) { // in case of async validation we should return undefined here, // because not all validations are finished yet return; } else { return valid; } }
...
}
// validate first
if (!options.validate) {
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
...
reset = function () { var obj = this; for (var k in obj) { if (k !== 'id' && !obj.constructor.dataSource.definitions[obj.constructor.modelName].properties[k]) { delete obj[k]; } } }
n/a
setStrict = function (strict) { this.__strict = strict; }
n/a
toJSON = function () { return this.toObject(false, true, false); }
...
if (err) {
if (done) done(err);
return;
}
mappings.forEach(function(m) {
var role;
if (options.returnOnlyRoleNames === true) {
role = m.toJSON().role.name;
} else {
role = m.roleId;
}
addRole(role);
});
if (done) done();
});
...
toObject = function (onlySchema, removeHidden, removeProtected) { if (onlySchema === undefined) { onlySchema = true; } var data = {}; var self = this; var Model = this.constructor; // if it is already an Object if (Model === Object) { return self; } var strict = this.__strict; var schemaLess = (strict === false) || !onlySchema; var persistUndefinedAsNull = Model.definition.settings.persistUndefinedAsNull; var props = Model.definition.properties; var keys = Object.keys(props); var propertyName, val; for (var i = 0; i < keys.length; i++) { propertyName = keys[i]; val = self[propertyName]; // Exclude functions if (typeof val === 'function') { continue; } // Exclude hidden properties if (removeHidden && Model.isHiddenProperty(propertyName)) { continue; } if (removeProtected && Model.isProtectedProperty(propertyName)) { continue; } if (val instanceof List) { data[propertyName] = val.toObject(!schemaLess, removeHidden, true); } else { if (val !== undefined && val !== null && val.toObject) { data[propertyName] = val.toObject(!schemaLess, removeHidden, true); } else { if (val === undefined && persistUndefinedAsNull) { val = null; } data[propertyName] = val; } } } if (schemaLess) { // Find its own properties which can be set via myModel.myProperty = 'myValue'. // If the property is not declared in the model definition, no setter will be // triggered to add it to __data keys = Object.keys(self); var size = keys.length; for (i = 0; i < size; i++) { propertyName = keys[i]; if (props[propertyName]) { continue; } if (propertyName.indexOf('__') === 0) { continue; } if (removeHidden && Model.isHiddenProperty(propertyName)) { continue; } if (removeProtected && Model.isProtectedProperty(propertyName)) { continue; } if (data[propertyName] !== undefined) { continue; } val = self[propertyName]; if (val !== undefined) { if (typeof val === 'function') { continue; } if (val !== null && val.toObject) { data[propertyName] = val.toObject(!schemaLess, removeHidden, true); } else { data[propertyName] = val; } } else if (persistUndefinedAsNull) { data[propertyName] = null; } } // Now continue to check __data keys = Object.keys(self.__data); size = keys.length; for (i = 0; i < size; i++) { propertyName = keys[i]; if (propertyName.indexOf('__') === 0) { continue; } if (data[propertyName] === undefined) { if (removeHidden && Model.isHiddenProperty(propertyName)) { continue; } if (removeProtected && Model.isProtectedProperty(propertyName)) { continue; } var ownVal = self[propertyName]; // The ownVal can be a relation function val = (ownVal !== undefined && (typeof ownVal !== 'function')) ? ownVal : self.__data[propertyName]; if (typeof val === 'function') { continue; } if (val !== undefined && val !== null && val.toObject) { data[propertyName] = val.toObject(!schemaLess, removeHidden, true); } else if (val === undefined && persistUndefinedAsNull) { data[propertyName] = null; } else { data[propertyName] = val; } } } } return data; }
...
options.validate = true;
}
if (!('throws' in options)) {
options.throws = false;
}
var inst = this;
var data = inst.toObject(true);
var id = this.getId();
if (!id) {
return Model.create(this, callback);
}
// validate first
...
function trigger(actionName, work, data, callback) { var capitalizedName = capitalize(actionName); var beforeHook = this.constructor['before' + capitalizedName] || this.constructor['pre' + capitalizedName]; var afterHook = this.constructor['after' + capitalizedName] || this.constructor['post' + capitalizedName]; if (actionName === 'validate') { beforeHook = beforeHook || this.constructor.beforeValidation; afterHook = afterHook || this.constructor.afterValidation; } var inst = this; if (actionName !== 'initialize') { if (beforeHook) deprecateHook(inst.constructor, ['before', 'pre'], capitalizedName); if (afterHook) deprecateHook(inst.constructor, ['after', 'post'], capitalizedName); } // we only call "before" hook when we have actual action (work) to perform if (work) { if (beforeHook) { // before hook should be called on instance with two parameters: next and data beforeHook.call(inst, function() { // Check arguments to next(err, result) if (arguments.length) { return callback && callback.apply(null, arguments); } // No err & result is present, proceed with the real work // actual action also have one param: callback work.call(inst, next); }, data); } else { work.call(inst, next); } } else { next(); } function next(done) { if (afterHook) { afterHook.call(inst, done); } else if (done) { done.call(this); } } }
...
}
callback(err, inst);
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
...
function GeoPoint(data) { if (!(this instanceof GeoPoint)) { return new GeoPoint(data); } if (arguments.length === 2) { data = { lat: arguments[0], lng: arguments[1], }; } assert(Array.isArray(data) || typeof data === 'object' || typeof data === 'string', 'must provide valid geo-coordinates array [lat, lng] or object or a "lat, lng" string'); if (typeof data === 'string') { try { data = JSON.parse(data); } catch (err) { data = data.split(/,\s*/); assert(data.length === 2, 'must provide a string "lat,lng" creating a GeoPoint with a string'); } } if (Array.isArray(data)) { data = { lat: Number(data[0]), lng: Number(data[1]), }; } else { data.lng = Number(data.lng); data.lat = Number(data.lat); } assert(typeof data === 'object', 'must provide a lat and lng object when creating a GeoPoint'); assert(typeof data.lat === 'number' && !isNaN(data.lat), 'lat must be a number when creating a GeoPoint'); assert(typeof data.lng === 'number' && !isNaN(data.lng), 'lng must be a number when creating a GeoPoint'); assert(data.lng <= 180, 'lng must be <= 180'); assert(data.lng >= -180, 'lng must be >= -180'); assert(data.lat <= 90, 'lat must be <= 90'); assert(data.lat >= -90, 'lat must be >= -90'); this.lat = data.lat; this.lng = data.lng; }
n/a
function distanceBetween(a, b, options) { if (!(a instanceof GeoPoint)) { a = GeoPoint(a); } if (!(b instanceof GeoPoint)) { b = GeoPoint(b); } var x1 = a.lat; var y1 = a.lng; var x2 = b.lat; var y2 = b.lng; return geoDistance(x1, y1, x2, y2, options); }
n/a
distanceTo = function (point, options) { return GeoPoint.distanceBetween(this, point, options); }
n/a
toString = function () { return this.lat + ',' + this.lng; }
...
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
if (err) return callback(err);
var modelIds = models.map(function(m) {
return m[idName].toString();
});
callback(null, changes.filter(function(ch) {
if (ch.type() === Change.DELETE) return true;
return modelIds.indexOf(ch.modelId) > -1;
}));
});
});
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
eventNames = function () { [native code] }
n/a
expire = function (key, ttl, options, callback) { throwNotAttached(this.modelName, 'expire'); }
...
* @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)
*/
KeyValueModel.expire = function(key, ttl, options, callback) {
throwNotAttached(this.modelName, 'expire');
};
/**
* Return the TTL (time to live) for a given key. TTL is the remaining time
...
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
get = function (key, options, callback) { throwNotAttached(this.modelName, 'get'); }
...
* supports Express middleware. See
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
...
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
iterateKeys = function (filter, options) { throwNotAttached(this.modelName, 'iterateKeys'); }
...
* 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`
* });
* });
* ```
...
keys = function (filter, options, callback) { throwNotAttached(this.modelName, 'keys'); }
...
modelConfig.remoting &&
modelConfig.remoting.sharedMethods &&
typeof modelConfig.remoting.sharedMethods === 'object';
if (modelConfigHasSharedMethodsSettings)
util._extend(settings, modelConfig.remoting.sharedMethods);
// validate setting values
Object.keys(settings).forEach(function(setting) {
var settingValue = settings[setting];
var settingValueType = typeof settingValue;
if (settingValueType !== 'boolean')
throw new TypeError(g.f('Expected boolean, got %s', settingValueType));
});
// set sharedMethod.shared using the merged settings
...
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
set = function (key, value, options, callback) { throwNotAttached(this.modelName, 'set'); }
...
*
* For example, to listen on the specified port and all hosts, and ignore app config.
* ```js
* app.listen(80);
* ```
*
* The function also installs a `listening` callback that calls
* `app.set('port')` with the value returned by `server.address().port`.
* This way the port param contains always the real port number, even when
* listen was called with port number 0.
*
* @param {Function} [cb] If specified, the callback is added as a listener
* for the server's "listening" event.
* @returns {http.Server} A node `http.Server` with this application configured
* as the request handler.
...
setMaxListeners = function () { [native code] }
n/a
setup = function () { KeyValueModel.base.setup.apply(this, arguments); this.remoteMethod('get', { accepts: { arg: 'key', type: 'string', required: true, http: {source: 'path'}, }, returns: {arg: 'value', type: 'any', root: true}, http: {path: '/:key', verb: 'get'}, rest: {after: convertNullToNotFoundError}, }); this.remoteMethod('set', { accepts: [ {arg: 'key', type: 'string', required: true, http: {source: 'path'}}, {arg: 'value', type: 'any', required: true, http: {source: 'body'}}, {arg: 'ttl', type: 'number', http: {source: 'query'}, description: 'time to live in milliseconds'}, ], http: {path: '/:key', verb: 'put'}, }); this.remoteMethod('expire', { accepts: [ {arg: 'key', type: 'string', required: true, http: {source: 'path'}}, {arg: 'ttl', type: 'number', required: true, http: {source: 'form'}}, ], http: {path: '/:key/expire', verb: 'put'}, }); this.remoteMethod('ttl', { accepts: { arg: 'key', type: 'string', required: true, http: {source: 'path'}, }, returns: {arg: 'value', type: 'any', root: true}, http: {path: '/:key/ttl', verb: 'get'}, }); this.remoteMethod('keys', { accepts: { arg: 'filter', type: 'object', required: false, http: {source: 'query'}, }, returns: {arg: 'keys', type: ['string'], root: true}, http: {path: '/keys', verb: 'get'}, }); }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.KeyValueModel.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.KeyValueModel.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
ttl = function (key, options, callback) { throwNotAttached(this.modelName, 'ttl'); }
...
* @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)
*/
KeyValueModel.ttl = function(key, options, callback) {
throwNotAttached(this.modelName, 'ttl');
};
/**
* Return all keys in the database.
...
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function MailConnector(settings) { assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object'); var transports = settings.transports; // if transports is not in settings object AND settings.transport exists if (!transports && settings.transport) { // then wrap single transport in an array and assign to transports transports = [settings.transport]; } if (!transports) { transports = []; } this.transportsIndex = {}; this.transports = []; if (loopback.isServer) { transports.forEach(this.setupTransport.bind(this)); } }
n/a
initialize = function (dataSource, callback) { dataSource.connector = new MailConnector(dataSource.settings); callback(); }
n/a
createTransport = function (transporter, defaults) { var urlConfig; var options; var mailer; var proxyUrl; // if no transporter configuration is provided use direct as default transporter = transporter || directTransport({ debug: true }); if ( // provided transporter is a configuration object, not transporter plugin (typeof transporter === 'object' && typeof transporter.send !== 'function') || // provided transporter looks like a connection url (typeof transporter === 'string' && /^(smtps?|direct):/i.test(transporter)) ) { if ((urlConfig = typeof transporter === 'string' ? transporter : transporter.url)) { // parse a configuration URL into configuration options options = shared.parseConnectionUrl(urlConfig); } else { options = transporter; } if (options.proxy && typeof options.proxy === 'string') { proxyUrl = options.proxy; } if (options.transport && typeof options.transport === 'string') { try { transporter = require('nodemailer-' + (options.transport).toLowerCase() + '-transport')(options); } catch (E) { // if transporter loader fails, return an error when sending mail transporter = { send: function (mail, callback) { var errmsg = 'Requested transport plugin "nodemailer-' + (options.transport).toLowerCase() + '-transport " could not be initiated'; var err = new Error(errmsg); err.code = 'EINIT'; setImmediate(function () { return callback(err); }); } }; } } else if (options.direct) { transporter = directTransport(options); } else if (options.pool) { transporter = smtpPoolTransport(options); } else { transporter = smtpTransport(options); } } mailer = new Nodemailer(transporter, options, defaults); if (proxyUrl) { setupProxy(mailer, proxyUrl); } return mailer; }
n/a
function Mailer() { }
n/a
defaultTransport = function () { return this.transports[0] || this.stubTransport; }
n/a
setupTransport = function (setting) { var connector = this; connector.transports = connector.transports || []; connector.transportsIndex = connector.transportsIndex || {}; var transport; var transportType = (setting.type || 'STUB').toLowerCase(); if (transportType === 'direct') { transport = mailer.createTransport(); } else if (transportType === 'smtp') { transport = mailer.createTransport(setting); } else { var transportModuleName = 'nodemailer-' + transportType + '-transport'; var transportModule = require(transportModuleName); transport = mailer.createTransport(transportModule(setting)); } connector.transportsIndex[setting.alias || setting.type] = transport; connector.transports.push(transport); }
n/a
transportForName = function (name) { return this.transportsIndex[name]; }
n/a
function Mailer() { }
n/a
send = function (options, fn) { var dataSource = this.dataSource; var settings = dataSource && dataSource.settings; var connector = dataSource.connector; assert(connector, 'Cannot send mail without a connector!'); var transport = connector.transportForName(options.transport); if (!transport) { transport = connector.defaultTransport(); } if (debug.enabled || settings && settings.debug) { g.log('Sending Mail:'); if (options.transport) { console.log(g.f('\t TRANSPORT:%s', options.transport)); } g.log('\t TO:%s', options.to); g.log('\t FROM:%s', options.from); g.log('\t SUBJECT:%s', options.subject); g.log('\t TEXT:%s', options.text); g.log('\t HTML:%s', options.html); } if (transport) { assert(transport.sendMail, 'You must supply an Email.settings.transports containing a valid transport'); transport.sendMail(options, fn); } else { g.warn('Warning: No email transport specified for sending email.' + ' Setup a transport to send mail messages.'); process.nextTick(function() { fn(null, options); }); } }
...
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
* @header var app = loopback()
...
send = function (fn) { this.constructor.send(this, fn); }
...
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
* @header var app = loopback()
...
function Memory() { // TODO implement entire memory connector }
n/a
function initializeDataSource(dataSource, callback) { dataSource.connector = new Memory(null, dataSource.settings); // Use dataSource.connect to avoid duplicate file reads from cache dataSource.connect(callback); }
n/a
function Connector(options) { EventEmitter.apply(this, arguments); this.options = options; debug('created with options', options); }
n/a
function RemoteConnector(settings) { assert(typeof settings === 'object', 'cannot initiaze RemoteConnector without a settings object'); this.client = settings.client; this.adapter = settings.adapter || 'rest'; this.protocol = settings.protocol || 'http'; this.root = settings.root || ''; this.host = settings.host || 'localhost'; this.port = settings.port || 3000; this.remotes = remoting.create(); this.name = 'remote-connector'; if (settings.url) { this.url = settings.url; } else { this.url = this.protocol + '://' + this.host + ':' + this.port + this.root; } // handle mixins in the define() method var DAO = this.DataAccessObject = function() { }; }
n/a
initialize = function (dataSource, callback) { var connector = dataSource.connector = new RemoteConnector(dataSource.settings); connector.connect(); process.nextTick(callback); }
n/a
connect = function () { this.remotes.connect(this.url, this.adapter); }
n/a
define = function (definition) { var Model = definition.model; var remotes = this.remotes; assert(Model.sharedClass, 'cannot attach ' + Model.modelName + ' to a remote connector without a Model.sharedClass'); jutil.mixin(Model, RelationMixin); jutil.mixin(Model, InclusionMixin); remotes.addClass(Model.sharedClass); this.resolve(Model); }
...
* @property {DataSource} Model.dataSource Data source to which the model is connected, if any. Static property.
* @property {SharedClass} Model.sharedMethod The `strong-remoting` [SharedClass](http://apidocs.strongloop.com/strong-remoting/#
sharedclass) that contains remoting (and http) metadata. Static property.
* @property {Object} settings Contains additional model settings.
* @property {string} settings.http.path Base URL of the model HTTP route.
* @property [{string}] settings.acls Array of ACLs for the model.
* @class
*/
var Model = registry.modelBuilder.define('Model');
Model.registry = registry;
/**
* The `loopback.Model.extend()` method calls this when you create a model that extends another model.
* Add any setup or configuration code you want executed when the model is created.
* See [Setting up a custom model](http://loopback.io/doc/en/lb2/Extending-built-in-models.html#setting-up-a-custom-model).
...
resolve = function (Model) { var remotes = this.remotes; Model.sharedClass.methods().forEach(function(remoteMethod) { if (remoteMethod.name !== 'Change' && remoteMethod.name !== 'Checkpoint') { createProxyMethod(Model, remotes, remoteMethod); } }); // setup a remoting type converter for this model remotes.defineObjectType(Model.modelName, function(data) { return new Model(data); }); }
...
},
},
mochaTest: {
'unit': {
src: 'test/*.js',
options: {
reporter: 'dot',
require: require.resolve('./test/helpers/use-english.js'),
},
},
'unit-xml': {
src: 'test/*.js',
options: {
reporter: 'xunit',
captureFile: 'xunit.xml',
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_defineChangeModel = function () { var BaseChangeModel = this.registry.getModel('Change'); assert(BaseChangeModel, 'Change model must be defined before enabling change replication'); const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {}; this.Change = BaseChangeModel.extend(this.modelName + '-change', additionalChangeModelProperties, {trackModel: this} ); if (this.dataSource) { attachRelatedModels(this); } // Re-attach related models whenever our datasource is changed. var self = this; this.on('dataSourceAttached', function() { attachRelatedModels(self); }); return this.Change; function attachRelatedModels(self) { self.Change.attachTo(self.dataSource); self.Change.getCheckpointModel().attachTo(self.dataSource); } }
...
// call Model.setup first
Model.setup.call(this);
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
bulkUpdate = function (updates, options, callback) { var tasks = []; var Model = this; var Change = this.getChangeModel(); var conflicts = []; var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof options === 'function') { options = {}; } options = options || {}; buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) { if (err) return callback(err); updates.forEach(function(update) { var id = update.change.modelId; var current = currentMap[id]; switch (update.type) { case Change.UPDATE: tasks.push(function(cb) { applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.CREATE: tasks.push(function(cb) { applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.DELETE: tasks.push(function(cb) { applyDelete(Model, id, current, update.change, conflicts, options, cb); }); break; } }); async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = {conflicts: conflicts}; return callback(err); } callback(); }); }); }
...
function bulkUpdate(_updates, cb) {
debug('\tstarting bulk update');
updates = _updates;
utils.uploadInChunks(
updates,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.bulkUpdate(smallArray, options, function(err) {
// bulk update is a special case where we want to process all chunks and aggregate all errors
chunkCallback(null, err);
});
},
function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) {
...
changes = function (since, filter, callback) { if (typeof since === 'function') { filter = {}; callback = since; since = -1; } if (typeof filter === 'function') { callback = filter; since = -1; filter = {}; } var idName = this.dataSource.idName(this.modelName); var Change = this.getChangeModel(); var model = this; const changeFilter = this.createChangeFilter(since, filter); filter = filter || {}; filter.fields = {}; filter.where = filter.where || {}; filter.fields[idName] = true; // TODO(ritch) this whole thing could be optimized a bit more Change.find(changeFilter, function(err, changes) { if (err) return callback(err); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); var ids = changes.map(function(change) { return change.getModelId(); }); filter.where[idName] = {inq: ids}; model.find(filter, function(err, models) { if (err) return callback(err); var modelIds = models.map(function(m) { return m[idName].toString(); }); callback(null, changes.filter(function(ch) { if (ch.type() === Change.DELETE) return true; return modelIds.indexOf(ch.modelId) > -1; })); }); }); }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
checkpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.bumpLastSeq(cb); }
...
}
cb(err);
});
}
function checkpoints() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) {
if (err) return cb(err);
newSourceCp = source.seq;
targetModel.checkpoint(function(err, target) {
if (err) return cb(err);
newTargetCp = target.seq;
debug('\tcreated checkpoints');
debug('\t\t%s for source model %s', newSourceCp, sourceModel.modelName);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
count = function (where, cb) { throwNotAttached(this.modelName, 'count'); }
n/a
create = function (data, callback) { throwNotAttached(this.modelName, 'create'); }
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
createChangeFilter = function (since, modelFilter) { return { where: { checkpoint: {gte: since}, modelName: this.modelName, }, }; }
...
since = -1;
filter = {};
}
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
var model = this;
const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
...
createChangeStream = function (options, cb) { if (typeof options === 'function') { cb = options; options = undefined; } var idName = this.getIdName(); var Model = this; var changes = new PassThrough({objectMode: true}); var writeable = true; changes.destroy = function() { changes.removeAllListeners('error'); changes.removeAllListeners('end'); writeable = false; changes = null; }; changes.on('error', function() { writeable = false; }); changes.on('end', function() { writeable = false; }); process.nextTick(function() { cb(null, changes); }); Model.observe('after save', createChangeHandler('save')); Model.observe('after delete', createChangeHandler('delete')); function createChangeHandler(type) { return function(ctx, next) { // since it might have set to null via destroy if (!changes) { return next(); } var where = ctx.where; var data = ctx.instance || ctx.data; var whereId = where && where[idName]; // the data includes the id // or the where includes the id var target; if (data && (data[idName] || data[idName] === 0)) { target = data[idName]; } else if (where && (where[idName] || where[idName] === 0)) { target = where[idName]; } var hasTarget = target === 0 || !!target; var change = { target: target, where: where, data: data, }; switch (type) { case 'save': if (ctx.isNewInstance === undefined) { change.type = hasTarget ? 'update' : 'create'; } else { change.type = ctx.isNewInstance ? 'create' : 'update'; } break; case 'delete': change.type = 'remove'; break; } // TODO(ritch) this is ugly... maybe a ReadableStream would be better if (writeable) { changes.write(change); } next(); }; } }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
createUpdates = function (deltas, cb) { var Change = this.getChangeModel(); var updates = []; var Model = this; var tasks = []; deltas.forEach(function(change) { change = new Change(change); var type = change.type(); var update = {type: type, change: change}; switch (type) { case Change.CREATE: case Change.UPDATE: tasks.push(function(cb) { Model.findById(change.modelId, function(err, inst) { if (err) return cb(err); if (!inst) { return cb && cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); } else { update.data = inst; } updates.push(update); cb(); }); }); break; case Change.DELETE: updates.push(update); break; } }); async.parallel(tasks, function(err) { if (err) return cb(err); cb(null, updates); }); }
...
if (diff && diff.deltas && diff.deltas.length) {
debug('\tbuilding a list of updates');
utils.uploadInChunks(
diff.deltas,
replicationChunkSize,
function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback);
},
cb);
} else {
// nothing to replicate
done();
}
}
...
currentCheckpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.current(cb); }
n/a
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
diff = function (since, remoteChanges, callback) { var Change = this.getChangeModel(); Change.diff(this.modelName, since, remoteChanges, callback); }
...
},
});
};
/**
* Get a set of deltas and conflicts since the given checkpoint.
*
* See [Change.diff()](#change-diff) for details.
*
* @param {Number} since Find deltas since this checkpoint.
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
...
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
enableChangeTracking = function () { var Model = this; var Change = this.Change || this._defineChangeModel(); var cleanupInterval = Model.settings.changeCleanupInterval || 30000; assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + ' is not attached to a dataSource'); var idName = this.getIdName(); var idProp = this.definition.properties[idName]; var idType = idProp && idProp.type; var idDefn = idProp && idProp.defaultFn; if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { deprecated('The model ' + this.modelName + ' is tracking changes, ' + 'which requires a string id with GUID/UUID default value.'); } Model.observe('after save', rectifyOnSave); Model.observe('after delete', rectifyOnDelete); // Only run if the run time is server // Can switch off cleanup by setting the interval to -1 if (runtime.isServer && cleanupInterval > 0) { // initial cleanup cleanup(); // cleanup setInterval(cleanup, cleanupInterval); } function cleanup() { Model.rectifyAllChanges(function(err) { if (err) { Model.handleChangeError(err, 'cleanup'); } }); } }
...
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
...
eventNames = function () { [native code] }
n/a
function exists(id, cb) { throwNotAttached(this.modelName, 'exists'); }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
function find(filter, cb) { throwNotAttached(this.modelName, 'find'); }
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, cb) { throwNotAttached(this.modelName, 'findById'); }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.Role.findById %s', id));
err.statusCode = 404;
...
findLastChange = function (id, cb) { var Change = this.getChangeModel(); Change.findOne({where: {modelId: id}}, cb); }
...
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
...
function findOne(filter, cb) { throwNotAttached(this.modelName, 'findOne'); }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, callback) { throwNotAttached(this.modelName, 'findOrCreate'); }
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getChangeModel = function () { var changeModel = this.Change; var isSetup = changeModel && changeModel.dataSource; assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); return changeModel; }
...
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback);
};
/**
* Get the changes to a model since the specified checkpoint. Provide a filter object
* to reduce the number of results returned.
* @param {Number} since Return only changes since this checkpoint.
...
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getIdName = function () { var Model = this; var ds = Model.getDataSource(); if (ds.idName) { return ds.idName(Model.modelName); } else { return 'id'; } }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
getRoles = function (context, options, callback) { if (!callback) { if (typeof options === 'function') { callback = options; options = {}; } else { callback = utils.createPromiseCallback(); } } if (!options) options = {}; context.registry = this.registry; if (!(context instanceof AccessContext)) { context = new AccessContext(context); } var roles = []; this.resolveRelatedModels(); var addRole = function(role) { if (role && roles.indexOf(role) === -1) { roles.push(role); } }; var self = this; // Check against the smart roles var inRoleTasks = []; Object.keys(Role.resolvers).forEach(function(role) { inRoleTasks.push(function(done) { self.isInRole(role, context, function(err, inRole) { if (debug.enabled) { debug('In role %j: %j', role, inRole); } if (!err && inRole) { addRole(role); done(); } else { done(err, null); } }); }); }); var roleMappingModel = this.roleMappingModel; context.principals.forEach(function(p) { // Check against the role mappings var principalType = p.type || undefined; var principalId = p.id == null ? undefined : p.id; if (typeof principalId !== 'string' && principalId != null) { principalId = principalId.toString(); } // Add the role itself if (principalType === RoleMapping.ROLE && principalId) { addRole(principalId); } if (principalType && principalId) { // Please find() treat undefined matches all values inRoleTasks.push(function(done) { var filter = {where: {principalType: principalType, principalId: principalId}}; if (options.returnOnlyRoleNames === true) { filter.include = ['role']; } roleMappingModel.find(filter, function(err, mappings) { debug('Role mappings found: %s %j', err, mappings); if (err) { if (done) done(err); return; } mappings.forEach(function(m) { var role; if (options.returnOnlyRoleNames === true) { role = m.toJSON().role.name; } else { role = m.roleId; } addRole(role); }); if (done) done(); }); }); } }); async.parallel(inRoleTasks, function(err, results) { debug('getRoles() returns: %j %j', err, roles); if (callback) callback(err, roles); }); return callback.promise; }
n/a
getSourceId = function (cb) { var dataSource = this.dataSource; if (!dataSource) { this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); } assert( dataSource.connector.name, 'Model.getSourceId: cannot get id without dataSource.connector.name' ); var id = [dataSource.connector.name, this.modelName].join('-'); cb(null, id); }
n/a
handleChangeError = function (err, operationName) { if (!err) return; this.emit('error', err, operationName); }
...
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance;
...
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
function isAuthenticated(context, callback) { if (!callback) callback = utils.createPromiseCallback(); process.nextTick(function() { if (callback) callback(null, context.isAuthenticated()); }); return callback.promise; }
...
debug('accessType %s', this.accessType);
if (this.accessToken) {
debug('accessToken:');
debug(' id %j', this.accessToken.id);
debug(' ttl %j', this.accessToken.ttl);
}
debug('getUserId() %s', this.getUserId());
debug('isAuthenticated() %s', this.isAuthenticated());
}
};
/**
* This class represents the abstract notion of a principal, which can be used
* to represent any entity, such as an individual, a corporation, and a login id
* @param {String} type The principal type
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isInRole = function (role, context, callback) { context.registry = this.registry; if (!(context instanceof AccessContext)) { context = new AccessContext(context); } if (!callback) { callback = utils.createPromiseCallback(); // historically, isInRole is returning the Role instance instead of true // we are preserving that behaviour for callback-based invocation, // but fixing it when invoked in Promise mode callback.promise = callback.promise.then(function(isInRole) { return !!isInRole; }); } this.resolveRelatedModels(); debug('isInRole(): %s', role); context.debug(); var resolver = Role.resolvers[role]; if (resolver) { debug('Custom resolver found for role %s', role); var promise = resolver(role, context, callback); if (promise && typeof promise.then === 'function') { promise.then( function(result) { callback(null, result); }, callback ); } return callback.promise; } if (context.principals.length === 0) { debug('isInRole() returns: false'); process.nextTick(function() { if (callback) callback(null, false); }); return callback.promise; } var inRole = context.principals.some(function(p) { var principalType = p.type || undefined; var principalId = p.id || undefined; // Check if it's the same role return principalType === RoleMapping.ROLE && principalId === role; }); if (inRole) { debug('isInRole() returns: %j', inRole); process.nextTick(function() { if (callback) callback(null, true); }); return callback.promise; } var roleMappingModel = this.roleMappingModel; this.findOne({where: {name: role}}, function(err, result) { if (err) { if (callback) callback(err); return; } if (!result) { if (callback) callback(null, false); return; } debug('Role found: %j', result); // Iterate through the list of principals async.some(context.principals, function(p, done) { var principalType = p.type || undefined; var principalId = p.id || undefined; var roleId = result.id.toString(); var principalIdIsString = typeof principalId === 'string'; if (principalId !== null && principalId !== undefined && !principalIdIsString) { principalId = principalId.toString(); } if (principalType && principalId) { roleMappingModel.findOne({where: {roleId: roleId, principalType: principalType, principalId: principalId}}, function(err, result) { debug('Role mapping found: %j', result); done(!err && result); // The only arg is the result }); } else { process.nextTick(function() { done(false); }); } }, function(inRole) { debug('isInRole() returns: %j', inRole); if (callback) callback(null, inRole); }); }); return callback.promise; }
...
return;
}
}
// Check role matches
if (acl.principalType === ACL.ROLE) {
inRoleTasks.push(function(done) {
roleModel.isInRole(acl.principalId, context,
function(err, inRole) {
if (!err && inRole) {
effectiveACLs.push(acl);
// add the role to authorizedRoles if allowed
if (acl.isAllowed(modelDefaultPermission))
authorizedRoles[acl.principalId] = true;
}
...
function isOwner(modelClass, modelId, userId, principalType, options, callback) { if (!callback && typeof options === 'function') { callback = options; options = {}; } else if (!callback && typeof principalType === 'function') { callback = principalType; principalType = undefined; options = {}; } principalType = principalType || Principal.USER; assert(modelClass, 'Model class is required'); if (!callback) callback = utils.createPromiseCallback(); debug('isOwner(): %s %s userId: %s principalType: %s', modelClass && modelClass.modelName, modelId, userId, principalType); // Return false if userId is missing if (!userId) { process.nextTick(function() { callback(null, false); }); return callback.promise; } // Is the modelClass User or a subclass of User? if (isUserClass(modelClass)) { var userModelName = modelClass.modelName; // matching ids is enough if principalType is USER or matches given user model name if (principalType === Principal.USER || principalType === userModelName) { process.nextTick(function() { callback(null, matches(modelId, userId)); }); } return callback.promise; } modelClass.findById(modelId, options, function(err, inst) { if (err || !inst) { debug('Model not found for id %j', modelId); return callback(err, false); } debug('Model found: %j', inst); // Historically, for principalType USER, we were resolving isOwner() // as true if the model has "userId" or "owner" property matching // id of the current user (principalId), even though there was no // belongsTo relation set up. var ownerId = inst.userId || inst.owner; if (principalType === Principal.USER && ownerId && 'function' !== typeof ownerId) { return callback(null, matches(ownerId, userId)); } // Try to follow belongsTo for (var r in modelClass.relations) { var rel = modelClass.relations[r]; // relation should be belongsTo and target a User based class var belongsToUser = rel.type === 'belongsTo' && isUserClass(rel.modelTo); if (!belongsToUser) { continue; } // checking related user var userModelName = rel.modelTo.modelName; if (principalType === Principal.USER || principalType === userModelName) { debug('Checking relation %s to %s: %j', r, userModelName, rel); inst[r](processRelatedUser); return; } } debug('No matching belongsTo relation found for model %j - user %j principalType %j', modelId, userId, principalType); callback(null, false); function processRelatedUser(err, user) { if (!err && user) { debug('User found: %j', user.id); callback(null, matches(user.id, userId)); } else { callback(err, false); } } }); return callback.promise; }
...
}
var modelClass = context.model;
var modelId = context.modelId;
var user = context.getUser();
var userId = user && user.id;
var principalType = user && user.principalType;
var opts = {accessToken: context.accessToken};
Role.isOwner(modelClass, modelId, userId, principalType, opts, callback);
});
function isUserClass(modelClass) {
if (!modelClass) return false;
var User = modelClass.modelBuilder.models.User;
if (!User) return false;
return modelClass == User || modelClass.prototype instanceof User;
...
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
n/a
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
rectifyAllChanges = function (callback) { this.getChangeModel().rectifyAll(callback); }
...
cleanup();
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
...
rectifyChange = function (id, callback) { var Change = this.getChangeModel(); Change.rectifyModelChanges(this.modelName, [id], callback); }
...
debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),
ctx.Model.modelName, id ? id : 'ALL');
debug('context instance:%j currentInstance:%j where:%j data %j',
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
}
if (id) {
ctx.Model.rectifyChange(id, reportErrorAndNext);
} else {
ctx.Model.rectifyAllChanges(reportErrorAndNext);
}
function reportErrorAndNext(err) {
if (err) {
ctx.Model.handleChangeError(err, 'after save');
...
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
registerResolver = function (role, resolver) { if (!Role.resolvers) { Role.resolvers = {}; } Role.resolvers[role] = resolver; }
...
Role.registerResolver = function(role, resolver) {
if (!Role.resolvers) {
Role.resolvers = {};
}
Role.resolvers[role] = resolver;
};
Role.registerResolver(Role.OWNER, function(role, context, callback) {
if (!context || !context.model || !context.modelId) {
process.nextTick(function() {
if (callback) callback(null, false);
});
return;
}
var modelClass = context.model;
...
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
n/a
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
function replaceById(id, data, cb) { throwNotAttached(this.modelName, 'replaceById'); }
n/a
function replaceOrCreate(data, callback) { throwNotAttached(this.modelName, 'replaceOrCreate'); }
n/a
replicate = function (since, targetModel, options, callback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof since === 'function' && since.modelName) { targetModel = since; since = -1; } if (typeof since !== 'object') { since = {source: since, target: since}; } if (typeof options === 'function') { options = {}; } options = options || {}; var sourceModel = this; callback = callback || utils.createPromiseCallback(); debug('replicating %s since %s to %s since %s', sourceModel.modelName, since.source, targetModel.modelName, since.target); if (options.filter) { debug('\twith filter %j', options.filter); } // In order to avoid a race condition between the replication and // other clients modifying the data, we must create the new target // checkpoint as the first step of the replication process. // As a side-effect of that, the replicated changes are associated // with the new target checkpoint. This is actually desired behaviour, // because that way clients replicating *from* the target model // since the new checkpoint will pick these changes up. // However, it increases the likelihood of (false) conflicts being detected. // In order to prevent that, we run the replication multiple times, // until no changes were replicated, but at most MAX_ATTEMPTS times // to prevent starvation. In most cases, the second run will find no changes // to replicate and we are done. var MAX_ATTEMPTS = 3; run(1, since); return callback.promise; function run(attempt, since) { debug('\titeration #%s', attempt); tryReplicate(sourceModel, targetModel, since, options, next); function next(err, conflicts, cps, updates) { var finished = err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS; if (finished) return callback(err, conflicts, cps); run(attempt + 1, cps); } } }
n/a
resolveRelatedModels = function () { if (!this.userModel) { var reg = this.registry; this.roleMappingModel = reg.getModelByType('RoleMapping'); this.userModel = reg.getModelByType('User'); this.applicationModel = reg.getModelByType('Application'); } }
...
* @callback {Function} callback Callback function
* @param {String|Error} err The error object.
* @param {AccessRequest} result The resolved access request.
*/
ACL.checkAccessForContext = function(context, callback) {
if (!callback) callback = utils.createPromiseCallback();
var self = this;
self.resolveRelatedModels();
var roleModel = self.roleModel;
if (!(context instanceof AccessContext)) {
context.registry = this.registry;
context = new AccessContext(context);
}
...
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
function setupPersistedModel() { // call Model.setup first Model.setup.call(this); var PersistedModel = this; // enable change tracking (usually for replication) if (this.settings.trackChanges) { PersistedModel._defineChangeModel(); PersistedModel.once('dataSourceAttached', function() { PersistedModel.enableChangeTracking(); }); } else if (this.settings.enableRemoteReplication) { PersistedModel._defineChangeModel(); } PersistedModel.setupRemoting(); }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupRemoting = function () { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; // This is just for LB 3.x options.replaceOnPUT = options.replaceOnPUT !== false; function setRemoting(scope, name, options) { var fn = scope[name]; fn._delegate = true; options.isStatic = scope === PersistedModel; PersistedModel.remoteMethod(name, options); } setRemoting(PersistedModel, 'create', { description: 'Create a new instance of the model and persist it into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, allowArray: true, description: 'Model instance data', http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/'}, }); var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], description: 'Patch an existing model instance or insert a new one ' + 'into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'patch', path: '/'}], }; if (!options.replaceOnPUT) { upsertOptions.http.unshift({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); var replaceOrCreateOptions = { description: 'Replace an existing model instance or insert a new one into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'post', path: '/replaceOrCreate'}], }; if (options.replaceOnPUT) { replaceOrCreateOptions.http.push({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'upsertWithWhere', { aliases: ['patchOrCreateWithWhere'], description: 'Update an existing model instance or insert a new one into ' + 'the data source based on the where criteria.', accessType: 'WRITE', accepts: [ {arg: 'where', type: 'object', http: {source: 'query'}, description: 'Criteria to match model instances'}, {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'An object of model property name/value pairs'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/upsertWithWhere'}, }); setRemoting(PersistedModel, 'exists', { description: 'Check whether a model instance exists in the data source.', accessType: 'READ', accepts: [ {arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'exists', type: 'boolean'}, http: [ {verb: 'get', path: '/:id/exists'}, {verb: 'head', path: '/:id'}, ], rest: { // After hook to map exists to 200/404 for HEAD after: function(ctx, cb) { if (ctx.req.method === 'GET') { // For GET, return {exists: true|false} as is return cb(); } if (!ctx.result.exists) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; cb(error); } else { cb(); } ...
...
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
/*!
* Throw an error telling the user that the method is not available and why.
*/
function throwNotAttached(modelName, methodName) {
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.Role.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.Role.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
/**
* 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'}, ...}
...
updateLastChange = function (id, data, cb) { var self = this; this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { err = new Error(g.f('No change record found for %s with id %s', self.modelName, id)); err.statusCode = 404; return cb(err); } inst.updateAttributes(data, cb); }); }
...
Conflict.prototype.resolve = function(cb) {
var conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{prev: targetChange.rev},
cb);
});
};
/**
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_defineChangeModel = function () { var BaseChangeModel = this.registry.getModel('Change'); assert(BaseChangeModel, 'Change model must be defined before enabling change replication'); const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {}; this.Change = BaseChangeModel.extend(this.modelName + '-change', additionalChangeModelProperties, {trackModel: this} ); if (this.dataSource) { attachRelatedModels(this); } // Re-attach related models whenever our datasource is changed. var self = this; this.on('dataSourceAttached', function() { attachRelatedModels(self); }); return this.Change; function attachRelatedModels(self) { self.Change.attachTo(self.dataSource); self.Change.getCheckpointModel().attachTo(self.dataSource); } }
...
// call Model.setup first
Model.setup.call(this);
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
bulkUpdate = function (updates, options, callback) { var tasks = []; var Model = this; var Change = this.getChangeModel(); var conflicts = []; var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof options === 'function') { options = {}; } options = options || {}; buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) { if (err) return callback(err); updates.forEach(function(update) { var id = update.change.modelId; var current = currentMap[id]; switch (update.type) { case Change.UPDATE: tasks.push(function(cb) { applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.CREATE: tasks.push(function(cb) { applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.DELETE: tasks.push(function(cb) { applyDelete(Model, id, current, update.change, conflicts, options, cb); }); break; } }); async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = {conflicts: conflicts}; return callback(err); } callback(); }); }); }
...
function bulkUpdate(_updates, cb) {
debug('\tstarting bulk update');
updates = _updates;
utils.uploadInChunks(
updates,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.bulkUpdate(smallArray, options, function(err) {
// bulk update is a special case where we want to process all chunks and aggregate all errors
chunkCallback(null, err);
});
},
function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) {
...
changes = function (since, filter, callback) { if (typeof since === 'function') { filter = {}; callback = since; since = -1; } if (typeof filter === 'function') { callback = filter; since = -1; filter = {}; } var idName = this.dataSource.idName(this.modelName); var Change = this.getChangeModel(); var model = this; const changeFilter = this.createChangeFilter(since, filter); filter = filter || {}; filter.fields = {}; filter.where = filter.where || {}; filter.fields[idName] = true; // TODO(ritch) this whole thing could be optimized a bit more Change.find(changeFilter, function(err, changes) { if (err) return callback(err); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); var ids = changes.map(function(change) { return change.getModelId(); }); filter.where[idName] = {inq: ids}; model.find(filter, function(err, models) { if (err) return callback(err); var modelIds = models.map(function(m) { return m[idName].toString(); }); callback(null, changes.filter(function(ch) { if (ch.type() === Change.DELETE) return true; return modelIds.indexOf(ch.modelId) > -1; })); }); }); }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
checkpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.bumpLastSeq(cb); }
...
}
cb(err);
});
}
function checkpoints() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) {
if (err) return cb(err);
newSourceCp = source.seq;
targetModel.checkpoint(function(err, target) {
if (err) return cb(err);
newTargetCp = target.seq;
debug('\tcreated checkpoints');
debug('\t\t%s for source model %s', newSourceCp, sourceModel.modelName);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
count = function (where, cb) { throwNotAttached(this.modelName, 'count'); }
n/a
create = function (data, callback) { throwNotAttached(this.modelName, 'create'); }
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
createChangeFilter = function (since, modelFilter) { return { where: { checkpoint: {gte: since}, modelName: this.modelName, }, }; }
...
since = -1;
filter = {};
}
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
var model = this;
const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
...
createChangeStream = function (options, cb) { if (typeof options === 'function') { cb = options; options = undefined; } var idName = this.getIdName(); var Model = this; var changes = new PassThrough({objectMode: true}); var writeable = true; changes.destroy = function() { changes.removeAllListeners('error'); changes.removeAllListeners('end'); writeable = false; changes = null; }; changes.on('error', function() { writeable = false; }); changes.on('end', function() { writeable = false; }); process.nextTick(function() { cb(null, changes); }); Model.observe('after save', createChangeHandler('save')); Model.observe('after delete', createChangeHandler('delete')); function createChangeHandler(type) { return function(ctx, next) { // since it might have set to null via destroy if (!changes) { return next(); } var where = ctx.where; var data = ctx.instance || ctx.data; var whereId = where && where[idName]; // the data includes the id // or the where includes the id var target; if (data && (data[idName] || data[idName] === 0)) { target = data[idName]; } else if (where && (where[idName] || where[idName] === 0)) { target = where[idName]; } var hasTarget = target === 0 || !!target; var change = { target: target, where: where, data: data, }; switch (type) { case 'save': if (ctx.isNewInstance === undefined) { change.type = hasTarget ? 'update' : 'create'; } else { change.type = ctx.isNewInstance ? 'create' : 'update'; } break; case 'delete': change.type = 'remove'; break; } // TODO(ritch) this is ugly... maybe a ReadableStream would be better if (writeable) { changes.write(change); } next(); }; } }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
createUpdates = function (deltas, cb) { var Change = this.getChangeModel(); var updates = []; var Model = this; var tasks = []; deltas.forEach(function(change) { change = new Change(change); var type = change.type(); var update = {type: type, change: change}; switch (type) { case Change.CREATE: case Change.UPDATE: tasks.push(function(cb) { Model.findById(change.modelId, function(err, inst) { if (err) return cb(err); if (!inst) { return cb && cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); } else { update.data = inst; } updates.push(update); cb(); }); }); break; case Change.DELETE: updates.push(update); break; } }); async.parallel(tasks, function(err) { if (err) return cb(err); cb(null, updates); }); }
...
if (diff && diff.deltas && diff.deltas.length) {
debug('\tbuilding a list of updates');
utils.uploadInChunks(
diff.deltas,
replicationChunkSize,
function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback);
},
cb);
} else {
// nothing to replicate
done();
}
}
...
currentCheckpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.current(cb); }
n/a
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
diff = function (since, remoteChanges, callback) { var Change = this.getChangeModel(); Change.diff(this.modelName, since, remoteChanges, callback); }
...
},
});
};
/**
* Get a set of deltas and conflicts since the given checkpoint.
*
* See [Change.diff()](#change-diff) for details.
*
* @param {Number} since Find deltas since this checkpoint.
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
...
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
enableChangeTracking = function () { var Model = this; var Change = this.Change || this._defineChangeModel(); var cleanupInterval = Model.settings.changeCleanupInterval || 30000; assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + ' is not attached to a dataSource'); var idName = this.getIdName(); var idProp = this.definition.properties[idName]; var idType = idProp && idProp.type; var idDefn = idProp && idProp.defaultFn; if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { deprecated('The model ' + this.modelName + ' is tracking changes, ' + 'which requires a string id with GUID/UUID default value.'); } Model.observe('after save', rectifyOnSave); Model.observe('after delete', rectifyOnDelete); // Only run if the run time is server // Can switch off cleanup by setting the interval to -1 if (runtime.isServer && cleanupInterval > 0) { // initial cleanup cleanup(); // cleanup setInterval(cleanup, cleanupInterval); } function cleanup() { Model.rectifyAllChanges(function(err) { if (err) { Model.handleChangeError(err, 'cleanup'); } }); } }
...
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
...
eventNames = function () { [native code] }
n/a
function exists(id, cb) { throwNotAttached(this.modelName, 'exists'); }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
function find(filter, cb) { throwNotAttached(this.modelName, 'find'); }
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, cb) { throwNotAttached(this.modelName, 'findById'); }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.RoleMapping.findById %s', id));
err.statusCode = 404;
...
findLastChange = function (id, cb) { var Change = this.getChangeModel(); Change.findOne({where: {modelId: id}}, cb); }
...
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
...
function findOne(filter, cb) { throwNotAttached(this.modelName, 'findOne'); }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, callback) { throwNotAttached(this.modelName, 'findOrCreate'); }
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getChangeModel = function () { var changeModel = this.Change; var isSetup = changeModel && changeModel.dataSource; assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); return changeModel; }
...
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback);
};
/**
* Get the changes to a model since the specified checkpoint. Provide a filter object
* to reduce the number of results returned.
* @param {Number} since Return only changes since this checkpoint.
...
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getIdName = function () { var Model = this; var ds = Model.getDataSource(); if (ds.idName) { return ds.idName(Model.modelName); } else { return 'id'; } }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
getSourceId = function (cb) { var dataSource = this.dataSource; if (!dataSource) { this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); } assert( dataSource.connector.name, 'Model.getSourceId: cannot get id without dataSource.connector.name' ); var id = [dataSource.connector.name, this.modelName].join('-'); cb(null, id); }
n/a
handleChangeError = function (err, operationName) { if (!err) return; this.emit('error', err, operationName); }
...
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance;
...
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
n/a
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
rectifyAllChanges = function (callback) { this.getChangeModel().rectifyAll(callback); }
...
cleanup();
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
...
rectifyChange = function (id, callback) { var Change = this.getChangeModel(); Change.rectifyModelChanges(this.modelName, [id], callback); }
...
debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),
ctx.Model.modelName, id ? id : 'ALL');
debug('context instance:%j currentInstance:%j where:%j data %j',
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
}
if (id) {
ctx.Model.rectifyChange(id, reportErrorAndNext);
} else {
ctx.Model.rectifyAllChanges(reportErrorAndNext);
}
function reportErrorAndNext(err) {
if (err) {
ctx.Model.handleChangeError(err, 'after save');
...
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
n/a
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
function replaceById(id, data, cb) { throwNotAttached(this.modelName, 'replaceById'); }
n/a
function replaceOrCreate(data, callback) { throwNotAttached(this.modelName, 'replaceOrCreate'); }
n/a
replicate = function (since, targetModel, options, callback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof since === 'function' && since.modelName) { targetModel = since; since = -1; } if (typeof since !== 'object') { since = {source: since, target: since}; } if (typeof options === 'function') { options = {}; } options = options || {}; var sourceModel = this; callback = callback || utils.createPromiseCallback(); debug('replicating %s since %s to %s since %s', sourceModel.modelName, since.source, targetModel.modelName, since.target); if (options.filter) { debug('\twith filter %j', options.filter); } // In order to avoid a race condition between the replication and // other clients modifying the data, we must create the new target // checkpoint as the first step of the replication process. // As a side-effect of that, the replicated changes are associated // with the new target checkpoint. This is actually desired behaviour, // because that way clients replicating *from* the target model // since the new checkpoint will pick these changes up. // However, it increases the likelihood of (false) conflicts being detected. // In order to prevent that, we run the replication multiple times, // until no changes were replicated, but at most MAX_ATTEMPTS times // to prevent starvation. In most cases, the second run will find no changes // to replicate and we are done. var MAX_ATTEMPTS = 3; run(1, since); return callback.promise; function run(attempt, since) { debug('\titeration #%s', attempt); tryReplicate(sourceModel, targetModel, since, options, next); function next(err, conflicts, cps, updates) { var finished = err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS; if (finished) return callback(err, conflicts, cps); run(attempt + 1, cps); } } }
n/a
resolveRelatedModels = function () { if (!this.userModel) { var reg = this.registry; this.roleModel = reg.getModelByType('Role'); this.userModel = reg.getModelByType('User'); this.applicationModel = reg.getModelByType('Application'); } }
...
* @callback {Function} callback Callback function
* @param {String|Error} err The error object.
* @param {AccessRequest} result The resolved access request.
*/
ACL.checkAccessForContext = function(context, callback) {
if (!callback) callback = utils.createPromiseCallback();
var self = this;
self.resolveRelatedModels();
var roleModel = self.roleModel;
if (!(context instanceof AccessContext)) {
context.registry = this.registry;
context = new AccessContext(context);
}
...
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
function setupPersistedModel() { // call Model.setup first Model.setup.call(this); var PersistedModel = this; // enable change tracking (usually for replication) if (this.settings.trackChanges) { PersistedModel._defineChangeModel(); PersistedModel.once('dataSourceAttached', function() { PersistedModel.enableChangeTracking(); }); } else if (this.settings.enableRemoteReplication) { PersistedModel._defineChangeModel(); } PersistedModel.setupRemoting(); }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupRemoting = function () { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; // This is just for LB 3.x options.replaceOnPUT = options.replaceOnPUT !== false; function setRemoting(scope, name, options) { var fn = scope[name]; fn._delegate = true; options.isStatic = scope === PersistedModel; PersistedModel.remoteMethod(name, options); } setRemoting(PersistedModel, 'create', { description: 'Create a new instance of the model and persist it into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, allowArray: true, description: 'Model instance data', http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/'}, }); var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], description: 'Patch an existing model instance or insert a new one ' + 'into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'patch', path: '/'}], }; if (!options.replaceOnPUT) { upsertOptions.http.unshift({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); var replaceOrCreateOptions = { description: 'Replace an existing model instance or insert a new one into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'post', path: '/replaceOrCreate'}], }; if (options.replaceOnPUT) { replaceOrCreateOptions.http.push({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'upsertWithWhere', { aliases: ['patchOrCreateWithWhere'], description: 'Update an existing model instance or insert a new one into ' + 'the data source based on the where criteria.', accessType: 'WRITE', accepts: [ {arg: 'where', type: 'object', http: {source: 'query'}, description: 'Criteria to match model instances'}, {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'An object of model property name/value pairs'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/upsertWithWhere'}, }); setRemoting(PersistedModel, 'exists', { description: 'Check whether a model instance exists in the data source.', accessType: 'READ', accepts: [ {arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'exists', type: 'boolean'}, http: [ {verb: 'get', path: '/:id/exists'}, {verb: 'head', path: '/:id'}, ], rest: { // After hook to map exists to 200/404 for HEAD after: function(ctx, cb) { if (ctx.req.method === 'GET') { // For GET, return {exists: true|false} as is return cb(); } if (!ctx.result.exists) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; cb(error); } else { cb(); } ...
...
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
/*!
* Throw an error telling the user that the method is not available and why.
*/
function throwNotAttached(modelName, methodName) {
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.RoleMapping.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.RoleMapping.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
/**
* 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'}, ...}
...
updateLastChange = function (id, data, cb) { var self = this; this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { err = new Error(g.f('No change record found for %s with id %s', self.modelName, id)); err.statusCode = 404; return cb(err); } inst.updateAttributes(data, cb); }); }
...
Conflict.prototype.resolve = function(cb) {
var conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{prev: targetChange.rev},
cb);
});
};
/**
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
application = function (callback) { callback = callback || utils.createPromiseCallback(); this.constructor.resolveRelatedModels(); if (this.principalType === RoleMapping.APPLICATION) { var applicationModel = this.constructor.applicationModel; applicationModel.findById(this.principalId, callback); } else { process.nextTick(function() { callback(null, null); }); } return callback.promise; }
n/a
childRole = function (callback) { callback = callback || utils.createPromiseCallback(); this.constructor.resolveRelatedModels(); if (this.principalType === RoleMapping.ROLE) { var roleModel = this.constructor.roleModel; roleModel.findById(this.principalId, callback); } else { process.nextTick(function() { callback(null, null); }); } return callback.promise; }
n/a
user = function (callback) { callback = callback || utils.createPromiseCallback(); this.constructor.resolveRelatedModels(); var userModel; if (this.principalType === RoleMapping.USER) { userModel = this.constructor.userModel; userModel.findById(this.principalId, callback); return callback.promise; } // try resolving a user model that matches principalType userModel = this.constructor.registry.findModel(this.principalType); if (userModel) { userModel.findById(this.principalId, callback); } else { process.nextTick(function() { callback(null, null); }); } return callback.promise; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function Route(path) { this.path = path; this.stack = []; debug('new %o', path) // route handlers for various http methods this.methods = {}; }
n/a
function _handles_method(method) { if (this.methods._all) { return true; } var name = method.toLowerCase(); if (name === 'head' && !this.methods['head']) { name = 'get'; } return Boolean(this.methods[name]); }
n/a
function _options() { var methods = Object.keys(this.methods); // append automatic head if (this.methods.get && !this.methods.head) { methods.push('head'); } for (var i = 0; i < methods.length; i++) { // make upper case methods[i] = methods[i].toUpperCase(); } return methods; }
n/a
acl = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
function all() { var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.all() requires callback functions but got a ' + type; throw new TypeError(msg); } var layer = Layer('/', {}, handle); layer.method = undefined; this.methods._all = true; this.stack.push(layer); } return this; }
n/a
bind = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
...
accepts: [
{arg: 'refresh', type: 'boolean', http: {source: 'query'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
],
description: format('Fetches hasOne relation %s.', relationName),
accessType: 'READ',
returns: {arg: relationName, type: relation.modelTo.modelName, root: true},
rest: {after: convertNullToNotFoundError.bind(null, toModelName)},
});
define('__create__' + relationName, {
isStatic: false,
http: {verb: 'post', path: '/' + pathName},
accepts: [
{
...
checkout = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
connect = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
copy = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
delete = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
function dispatch(req, res, done) { var idx = 0; var stack = this.stack; if (stack.length === 0) { return done(); } var method = req.method.toLowerCase(); if (method === 'head' && !this.methods['head']) { method = 'get'; } req.route = this; next(); function next(err) { // signal to exit route if (err && err === 'route') { return done(); } // signal to exit router if (err && err === 'router') { return done(err) } var layer = stack[idx++]; if (!layer) { return done(err); } if (layer.method && layer.method !== method) { return next(err); } if (err) { layer.handle_error(err, req, res, next); } else { layer.handle_request(req, res, next); } } }
n/a
get = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
...
* supports Express middleware. See
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
...
head = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
link = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
lock = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
m-search = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
merge = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
mkactivity = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
mkcalendar = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
mkcol = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
move = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
notify = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
options = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
patch = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
post = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
propfind = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
proppatch = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
purge = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
put = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
rebind = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
report = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
search = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
subscribe = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
trace = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
unbind = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
unlink = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
unlock = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
unsubscribe = function (){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }
n/a
Router = function (options) { var opts = options || {}; function router(req, res, next) { router.handle(req, res, next); } // mixin Router class functions setPrototypeOf(router, proto) router.params = {}; router._params = []; router.caseSensitive = opts.caseSensitive; router.mergeParams = opts.mergeParams; router.strict = opts.strict; router.stack = []; return router; }
n/a
acl = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
all = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
bind = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
...
accepts: [
{arg: 'refresh', type: 'boolean', http: {source: 'query'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
],
description: format('Fetches hasOne relation %s.', relationName),
accessType: 'READ',
returns: {arg: relationName, type: relation.modelTo.modelName, root: true},
rest: {after: convertNullToNotFoundError.bind(null, toModelName)},
});
define('__create__' + relationName, {
isStatic: false,
http: {verb: 'post', path: '/' + pathName},
accepts: [
{
...
checkout = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
connect = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
copy = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
delete = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
get = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
...
* supports Express middleware. See
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
...
function handle(req, res, out) { var self = this; debug('dispatching %s %s', req.method, req.url); var idx = 0; var protohost = getProtohost(req.url) || '' var removed = ''; var slashAdded = false; var paramcalled = {}; // store options for OPTIONS request // only used if OPTIONS request var options = []; // middleware and routes var stack = self.stack; // manage inter-router variables var parentParams = req.params; var parentUrl = req.baseUrl || ''; var done = restore(out, req, 'baseUrl', 'next', 'params'); // setup next layer req.next = next; // for options requests, respond with a default if nothing else responds if (req.method === 'OPTIONS') { done = wrap(done, function(old, err) { if (err || options.length === 0) return old(err); sendOptionsResponse(res, options, old); }); } // setup basic req values req.baseUrl = parentUrl; req.originalUrl = req.originalUrl || req.url; next(); function next(err) { var layerError = err === 'route' ? null : err; // remove added slash if (slashAdded) { req.url = req.url.substr(1); slashAdded = false; } // restore altered req.url if (removed.length !== 0) { req.baseUrl = parentUrl; req.url = protohost + removed + req.url.substr(protohost.length); removed = ''; } // signal to exit router if (layerError === 'router') { setImmediate(done, null) return } // no more matching layers if (idx >= stack.length) { setImmediate(done, layerError); return; } // get pathname of request var path = getPathname(req); if (path == null) { return done(layerError); } // find next matching layer var layer; var match; var route; while (match !== true && idx < stack.length) { layer = stack[idx++]; match = matchLayer(layer, path); route = layer.route; if (typeof match !== 'boolean') { // hold on to layerError layerError = layerError || match; } if (match !== true) { continue; } if (!route) { // process non-route handlers normally continue; } if (layerError) { // routes do not match with a pending error match = false; continue; } var method = req.method; var has_method = route._handles_method(method); // build up automatic options response if (!has_method && method === 'OPTIONS') { appendMethods(options, route._options()); } // don't even bother matching route if (!has_method && method !== 'HEAD') { match = false; continue; } } // no match if (match !== true) { return done(layerError); } // store route for dispatch on change if (route) { req.route = route; } // Capture one-time layer values req.params = self.mergeParams ? mergeParams(layer.params, parentParams) : layer.params; var layerPath = layer.path; // this should be done for the layer self.process_params(layer, paramcalled, req, res, function (err) { if (err) { return next(layerError || err); } if (route) { return layer.handle_request(req, res, next); } trim_prefix(layer, layerError, layerPath, path); }); } function trim_prefix(layer, layerError, layerPath, path) { if (layerPath.length !== 0) { // Validate path breaks on a path separator var c = path[layerPath.length] if (c && c !== '/' && c !== '.') return next(layerError) // Trim off the part of the url that matches the route // middleware (.use stuff) needs to have the path stripped debug('trim prefix (%s) from url %s', layerPath, req.url); removed = layerPath; req.url = protohost + req.url.substr(protohost.length + removed.length); // Ensure leading slash if (!protohost && req.url[0] !== '/') { req.url = '/' + req.url; slashAdded = true; ...
n/a
head = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
link = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
lock = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
m-search = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
merge = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
mkactivity = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
mkcalendar = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
mkcol = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
move = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
notify = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
options = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function param(name, fn) { // param logic if (typeof name === 'function') { deprecate('router.param(fn): Refactor to use path params'); this._params.push(name); return; } // apply param functions var params = this._params; var len = params.length; var ret; if (name[0] === ':') { deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.substr(1)) + ', fn) instead '); name = name.substr(1); } for (var i = 0; i < len; ++i) { if (ret = params[i](name, fn)) { fn = ret; } } // ensure we end up with a // middleware function if ('function' !== typeof fn) { throw new Error('invalid param() call for ' + name + ', got ' + fn); } (this.params[name] = this.params[name] || []).push(fn); return this; }
...
remotes.authorization = function(ctx, next) {
var method = ctx.method;
var req = ctx.req;
var Model = method.ctor;
var modelInstance = ctx.instance;
var modelId = modelInstance && modelInstance.id ||
// replacement for deprecated req.param()
(req.params && req.params.id !== undefined ? req.params.id :
req.body && req.body.id !== undefined ? req.body.id :
req.query && req.query.id !== undefined ? req.query.id :
undefined);
var modelName = Model.modelName;
...
patch = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
post = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function process_params(layer, called, req, res, done) { var params = this.params; // captured parameters from the layer, keys and values var keys = layer.keys; // fast track if (!keys || keys.length === 0) { return done(); } var i = 0; var name; var paramIndex = 0; var key; var paramVal; var paramCallbacks; var paramCalled; // process params in order // param callbacks can be async function param(err) { if (err) { return done(err); } if (i >= keys.length ) { return done(); } paramIndex = 0; key = keys[i++]; name = key.name; paramVal = req.params[name]; paramCallbacks = params[name]; paramCalled = called[name]; if (paramVal === undefined || !paramCallbacks) { return param(); } // param previously called with same value or error occurred if (paramCalled && (paramCalled.match === paramVal || (paramCalled.error && paramCalled.error !== 'route'))) { // restore value req.params[name] = paramCalled.value; // next param return param(paramCalled.error); } called[name] = paramCalled = { error: null, match: paramVal, value: paramVal }; paramCallback(); } // single param callbacks function paramCallback(err) { var fn = paramCallbacks[paramIndex++]; // store updated value paramCalled.value = req.params[key.name]; if (err) { // store error paramCalled.error = err; param(err); return; } if (!fn) return param(); try { fn(req, res, paramCallback, paramVal, key.name); } catch (e) { paramCallback(e); } } param(); }
n/a
propfind = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
proppatch = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
purge = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
put = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
rebind = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
report = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function route(path) { var route = new Route(path); var layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; }
n/a
search = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
subscribe = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
trace = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
unbind = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
unlink = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
unlock = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
unsubscribe = function (path){ var route = this.route(path) route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function use(fn) { var offset = 0; var path = '/'; // default path to '/' // disambiguate router.use([fn]) if (typeof fn !== 'function') { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; } // first arg is the path if (typeof arg !== 'function') { offset = 1; path = fn; } } var callbacks = flatten(slice.call(arguments, offset)); if (callbacks.length === 0) { throw new TypeError('Router.use() requires middleware functions'); } for (var i = 0; i < callbacks.length; i++) { var fn = callbacks[i]; if (typeof fn !== 'function') { throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn)); } // add the middleware debug('use %o %s', path, fn.name || '<anonymous>') var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; this.stack.push(layer); } return this; }
...
if (this._requestHandlingPhases.indexOf(name) === -1)
throw new Error(g.f('Unknown {{middleware}} phase %s', name));
debug('use %s %s %s', fullPhaseName, paths, handlerName);
this._skipLayerSorting = true;
this.use(paths, handler);
var layer = this._findLayerByHandler(handler);
if (layer) {
// Set the phase name for sorting
layer.phase = fullPhaseName;
} else {
debug('No matching layer is found for %s %s', fullPhaseName, handlerName);
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_defineChangeModel = function () { var BaseChangeModel = this.registry.getModel('Change'); assert(BaseChangeModel, 'Change model must be defined before enabling change replication'); const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {}; this.Change = BaseChangeModel.extend(this.modelName + '-change', additionalChangeModelProperties, {trackModel: this} ); if (this.dataSource) { attachRelatedModels(this); } // Re-attach related models whenever our datasource is changed. var self = this; this.on('dataSourceAttached', function() { attachRelatedModels(self); }); return this.Change; function attachRelatedModels(self) { self.Change.attachTo(self.dataSource); self.Change.getCheckpointModel().attachTo(self.dataSource); } }
...
// call Model.setup first
Model.setup.call(this);
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
bulkUpdate = function (updates, options, callback) { var tasks = []; var Model = this; var Change = this.getChangeModel(); var conflicts = []; var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof options === 'function') { options = {}; } options = options || {}; buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) { if (err) return callback(err); updates.forEach(function(update) { var id = update.change.modelId; var current = currentMap[id]; switch (update.type) { case Change.UPDATE: tasks.push(function(cb) { applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.CREATE: tasks.push(function(cb) { applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.DELETE: tasks.push(function(cb) { applyDelete(Model, id, current, update.change, conflicts, options, cb); }); break; } }); async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = {conflicts: conflicts}; return callback(err); } callback(); }); }); }
...
function bulkUpdate(_updates, cb) {
debug('\tstarting bulk update');
updates = _updates;
utils.uploadInChunks(
updates,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.bulkUpdate(smallArray, options, function(err) {
// bulk update is a special case where we want to process all chunks and aggregate all errors
chunkCallback(null, err);
});
},
function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) {
...
changes = function (since, filter, callback) { if (typeof since === 'function') { filter = {}; callback = since; since = -1; } if (typeof filter === 'function') { callback = filter; since = -1; filter = {}; } var idName = this.dataSource.idName(this.modelName); var Change = this.getChangeModel(); var model = this; const changeFilter = this.createChangeFilter(since, filter); filter = filter || {}; filter.fields = {}; filter.where = filter.where || {}; filter.fields[idName] = true; // TODO(ritch) this whole thing could be optimized a bit more Change.find(changeFilter, function(err, changes) { if (err) return callback(err); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); var ids = changes.map(function(change) { return change.getModelId(); }); filter.where[idName] = {inq: ids}; model.find(filter, function(err, models) { if (err) return callback(err); var modelIds = models.map(function(m) { return m[idName].toString(); }); callback(null, changes.filter(function(ch) { if (ch.type() === Change.DELETE) return true; return modelIds.indexOf(ch.modelId) > -1; })); }); }); }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
checkPermission = function (scope, model, property, accessType, callback) { this.resolveRelatedModels(); var aclModel = this.aclModel; assert(aclModel, 'ACL model must be defined before Scope.checkPermission is called'); this.findOne({where: {name: scope}}, function(err, scope) { if (err) { if (callback) callback(err); } else { aclModel.checkPermission( aclModel.SCOPE, scope.id, model, property, accessType, callback); } }); }
...
assert(aclModel,
'ACL model must be defined before Scope.checkPermission is called');
this.findOne({where: {name: scope}}, function(err, scope) {
if (err) {
if (callback) callback(err);
} else {
aclModel.checkPermission(
aclModel.SCOPE, scope.id, model, property, accessType, callback);
}
});
};
};
...
checkpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.bumpLastSeq(cb); }
...
}
cb(err);
});
}
function checkpoints() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) {
if (err) return cb(err);
newSourceCp = source.seq;
targetModel.checkpoint(function(err, target) {
if (err) return cb(err);
newTargetCp = target.seq;
debug('\tcreated checkpoints');
debug('\t\t%s for source model %s', newSourceCp, sourceModel.modelName);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
count = function (where, cb) { throwNotAttached(this.modelName, 'count'); }
n/a
create = function (data, callback) { throwNotAttached(this.modelName, 'create'); }
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
createChangeFilter = function (since, modelFilter) { return { where: { checkpoint: {gte: since}, modelName: this.modelName, }, }; }
...
since = -1;
filter = {};
}
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
var model = this;
const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
...
createChangeStream = function (options, cb) { if (typeof options === 'function') { cb = options; options = undefined; } var idName = this.getIdName(); var Model = this; var changes = new PassThrough({objectMode: true}); var writeable = true; changes.destroy = function() { changes.removeAllListeners('error'); changes.removeAllListeners('end'); writeable = false; changes = null; }; changes.on('error', function() { writeable = false; }); changes.on('end', function() { writeable = false; }); process.nextTick(function() { cb(null, changes); }); Model.observe('after save', createChangeHandler('save')); Model.observe('after delete', createChangeHandler('delete')); function createChangeHandler(type) { return function(ctx, next) { // since it might have set to null via destroy if (!changes) { return next(); } var where = ctx.where; var data = ctx.instance || ctx.data; var whereId = where && where[idName]; // the data includes the id // or the where includes the id var target; if (data && (data[idName] || data[idName] === 0)) { target = data[idName]; } else if (where && (where[idName] || where[idName] === 0)) { target = where[idName]; } var hasTarget = target === 0 || !!target; var change = { target: target, where: where, data: data, }; switch (type) { case 'save': if (ctx.isNewInstance === undefined) { change.type = hasTarget ? 'update' : 'create'; } else { change.type = ctx.isNewInstance ? 'create' : 'update'; } break; case 'delete': change.type = 'remove'; break; } // TODO(ritch) this is ugly... maybe a ReadableStream would be better if (writeable) { changes.write(change); } next(); }; } }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
createUpdates = function (deltas, cb) { var Change = this.getChangeModel(); var updates = []; var Model = this; var tasks = []; deltas.forEach(function(change) { change = new Change(change); var type = change.type(); var update = {type: type, change: change}; switch (type) { case Change.CREATE: case Change.UPDATE: tasks.push(function(cb) { Model.findById(change.modelId, function(err, inst) { if (err) return cb(err); if (!inst) { return cb && cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); } else { update.data = inst; } updates.push(update); cb(); }); }); break; case Change.DELETE: updates.push(update); break; } }); async.parallel(tasks, function(err) { if (err) return cb(err); cb(null, updates); }); }
...
if (diff && diff.deltas && diff.deltas.length) {
debug('\tbuilding a list of updates');
utils.uploadInChunks(
diff.deltas,
replicationChunkSize,
function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback);
},
cb);
} else {
// nothing to replicate
done();
}
}
...
currentCheckpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.current(cb); }
n/a
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
diff = function (since, remoteChanges, callback) { var Change = this.getChangeModel(); Change.diff(this.modelName, since, remoteChanges, callback); }
...
},
});
};
/**
* Get a set of deltas and conflicts since the given checkpoint.
*
* See [Change.diff()](#change-diff) for details.
*
* @param {Number} since Find deltas since this checkpoint.
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
...
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
enableChangeTracking = function () { var Model = this; var Change = this.Change || this._defineChangeModel(); var cleanupInterval = Model.settings.changeCleanupInterval || 30000; assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + ' is not attached to a dataSource'); var idName = this.getIdName(); var idProp = this.definition.properties[idName]; var idType = idProp && idProp.type; var idDefn = idProp && idProp.defaultFn; if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { deprecated('The model ' + this.modelName + ' is tracking changes, ' + 'which requires a string id with GUID/UUID default value.'); } Model.observe('after save', rectifyOnSave); Model.observe('after delete', rectifyOnDelete); // Only run if the run time is server // Can switch off cleanup by setting the interval to -1 if (runtime.isServer && cleanupInterval > 0) { // initial cleanup cleanup(); // cleanup setInterval(cleanup, cleanupInterval); } function cleanup() { Model.rectifyAllChanges(function(err) { if (err) { Model.handleChangeError(err, 'cleanup'); } }); } }
...
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
...
eventNames = function () { [native code] }
n/a
function exists(id, cb) { throwNotAttached(this.modelName, 'exists'); }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
function find(filter, cb) { throwNotAttached(this.modelName, 'find'); }
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, cb) { throwNotAttached(this.modelName, 'findById'); }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.Scope.findById %s', id));
err.statusCode = 404;
...
findLastChange = function (id, cb) { var Change = this.getChangeModel(); Change.findOne({where: {modelId: id}}, cb); }
...
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
...
function findOne(filter, cb) { throwNotAttached(this.modelName, 'findOne'); }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, callback) { throwNotAttached(this.modelName, 'findOrCreate'); }
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getChangeModel = function () { var changeModel = this.Change; var isSetup = changeModel && changeModel.dataSource; assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); return changeModel; }
...
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback);
};
/**
* Get the changes to a model since the specified checkpoint. Provide a filter object
* to reduce the number of results returned.
* @param {Number} since Return only changes since this checkpoint.
...
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getIdName = function () { var Model = this; var ds = Model.getDataSource(); if (ds.idName) { return ds.idName(Model.modelName); } else { return 'id'; } }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
getSourceId = function (cb) { var dataSource = this.dataSource; if (!dataSource) { this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); } assert( dataSource.connector.name, 'Model.getSourceId: cannot get id without dataSource.connector.name' ); var id = [dataSource.connector.name, this.modelName].join('-'); cb(null, id); }
n/a
handleChangeError = function (err, operationName) { if (!err) return; this.emit('error', err, operationName); }
...
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance;
...
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
n/a
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
rectifyAllChanges = function (callback) { this.getChangeModel().rectifyAll(callback); }
...
cleanup();
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
...
rectifyChange = function (id, callback) { var Change = this.getChangeModel(); Change.rectifyModelChanges(this.modelName, [id], callback); }
...
debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),
ctx.Model.modelName, id ? id : 'ALL');
debug('context instance:%j currentInstance:%j where:%j data %j',
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
}
if (id) {
ctx.Model.rectifyChange(id, reportErrorAndNext);
} else {
ctx.Model.rectifyAllChanges(reportErrorAndNext);
}
function reportErrorAndNext(err) {
if (err) {
ctx.Model.handleChangeError(err, 'after save');
...
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
n/a
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
function replaceById(id, data, cb) { throwNotAttached(this.modelName, 'replaceById'); }
n/a
function replaceOrCreate(data, callback) { throwNotAttached(this.modelName, 'replaceOrCreate'); }
n/a
replicate = function (since, targetModel, options, callback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof since === 'function' && since.modelName) { targetModel = since; since = -1; } if (typeof since !== 'object') { since = {source: since, target: since}; } if (typeof options === 'function') { options = {}; } options = options || {}; var sourceModel = this; callback = callback || utils.createPromiseCallback(); debug('replicating %s since %s to %s since %s', sourceModel.modelName, since.source, targetModel.modelName, since.target); if (options.filter) { debug('\twith filter %j', options.filter); } // In order to avoid a race condition between the replication and // other clients modifying the data, we must create the new target // checkpoint as the first step of the replication process. // As a side-effect of that, the replicated changes are associated // with the new target checkpoint. This is actually desired behaviour, // because that way clients replicating *from* the target model // since the new checkpoint will pick these changes up. // However, it increases the likelihood of (false) conflicts being detected. // In order to prevent that, we run the replication multiple times, // until no changes were replicated, but at most MAX_ATTEMPTS times // to prevent starvation. In most cases, the second run will find no changes // to replicate and we are done. var MAX_ATTEMPTS = 3; run(1, since); return callback.promise; function run(attempt, since) { debug('\titeration #%s', attempt); tryReplicate(sourceModel, targetModel, since, options, next); function next(err, conflicts, cps, updates) { var finished = err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS; if (finished) return callback(err, conflicts, cps); run(attempt + 1, cps); } } }
n/a
resolveRelatedModels = function () { if (!this.aclModel) { var reg = this.registry; this.aclModel = reg.getModelByType(loopback.ACL); } }
...
* @callback {Function} callback Callback function
* @param {String|Error} err The error object.
* @param {AccessRequest} result The resolved access request.
*/
ACL.checkAccessForContext = function(context, callback) {
if (!callback) callback = utils.createPromiseCallback();
var self = this;
self.resolveRelatedModels();
var roleModel = self.roleModel;
if (!(context instanceof AccessContext)) {
context.registry = this.registry;
context = new AccessContext(context);
}
...
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
function setupPersistedModel() { // call Model.setup first Model.setup.call(this); var PersistedModel = this; // enable change tracking (usually for replication) if (this.settings.trackChanges) { PersistedModel._defineChangeModel(); PersistedModel.once('dataSourceAttached', function() { PersistedModel.enableChangeTracking(); }); } else if (this.settings.enableRemoteReplication) { PersistedModel._defineChangeModel(); } PersistedModel.setupRemoting(); }
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupRemoting = function () { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; // This is just for LB 3.x options.replaceOnPUT = options.replaceOnPUT !== false; function setRemoting(scope, name, options) { var fn = scope[name]; fn._delegate = true; options.isStatic = scope === PersistedModel; PersistedModel.remoteMethod(name, options); } setRemoting(PersistedModel, 'create', { description: 'Create a new instance of the model and persist it into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, allowArray: true, description: 'Model instance data', http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/'}, }); var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], description: 'Patch an existing model instance or insert a new one ' + 'into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'patch', path: '/'}], }; if (!options.replaceOnPUT) { upsertOptions.http.unshift({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); var replaceOrCreateOptions = { description: 'Replace an existing model instance or insert a new one into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'post', path: '/replaceOrCreate'}], }; if (options.replaceOnPUT) { replaceOrCreateOptions.http.push({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'upsertWithWhere', { aliases: ['patchOrCreateWithWhere'], description: 'Update an existing model instance or insert a new one into ' + 'the data source based on the where criteria.', accessType: 'WRITE', accepts: [ {arg: 'where', type: 'object', http: {source: 'query'}, description: 'Criteria to match model instances'}, {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'An object of model property name/value pairs'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/upsertWithWhere'}, }); setRemoting(PersistedModel, 'exists', { description: 'Check whether a model instance exists in the data source.', accessType: 'READ', accepts: [ {arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'exists', type: 'boolean'}, http: [ {verb: 'get', path: '/:id/exists'}, {verb: 'head', path: '/:id'}, ], rest: { // After hook to map exists to 200/404 for HEAD after: function(ctx, cb) { if (ctx.req.method === 'GET') { // For GET, return {exists: true|false} as is return cb(); } if (!ctx.result.exists) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; cb(error); } else { cb(); } ...
...
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
/*!
* Throw an error telling the user that the method is not available and why.
*/
function throwNotAttached(modelName, methodName) {
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.Scope.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.Scope.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
/**
* 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'}, ...}
...
updateLastChange = function (id, data, cb) { var self = this; this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { err = new Error(g.f('No change record found for %s with id %s', self.modelName, id)); err.statusCode = 404; return cb(err); } inst.updateAttributes(data, cb); }); }
...
Conflict.prototype.resolve = function(cb) {
var conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{prev: targetChange.rev},
cb);
});
};
/**
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function getACL(ACL) { var registry = this.registry; if (ACL !== undefined) { // The function is used as a setter _aclModel = ACL; } if (_aclModel) { return _aclModel; } var aclModel = registry.getModel('ACL'); _aclModel = registry.getModelByType(aclModel); return _aclModel; }
...
* @param {String|Error} err The error object.
* @param {Boolean} allowed True if the request is allowed; false otherwise.
*/
Model.checkAccess = function(token, modelId, sharedMethod, ctx, callback) {
var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS;
token = token || ANONYMOUS;
var aclModel = Model._ACL();
ctx = ctx || {};
if (typeof ctx === 'function' && callback === undefined) {
callback = ctx;
ctx = {};
}
...
_defineChangeModel = function () { var BaseChangeModel = this.registry.getModel('Change'); assert(BaseChangeModel, 'Change model must be defined before enabling change replication'); const additionalChangeModelProperties = this.settings.additionalChangeModelProperties || {}; this.Change = BaseChangeModel.extend(this.modelName + '-change', additionalChangeModelProperties, {trackModel: this} ); if (this.dataSource) { attachRelatedModels(this); } // Re-attach related models whenever our datasource is changed. var self = this; this.on('dataSourceAttached', function() { attachRelatedModels(self); }); return this.Change; function attachRelatedModels(self) { self.Change.attachTo(self.dataSource); self.Change.getCheckpointModel().attachTo(self.dataSource); } }
...
// call Model.setup first
Model.setup.call(this);
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
...
_getAccessTypeForMethod = function (method) { if (typeof method === 'string') { method = {name: method}; } assert( typeof method === 'object', 'method is a required argument and must be a RemoteMethod object' ); var ACL = Model._ACL(); // Check the explicit setting of accessType if (method.accessType) { assert(method.accessType === ACL.READ || method.accessType === ACL.REPLICATE || method.accessType === ACL.WRITE || method.accessType === ACL.EXECUTE, 'invalid accessType ' + method.accessType + '. It must be "READ", "REPLICATE", "WRITE", or "EXECUTE"'); return method.accessType; } // Default GET requests to READ var verb = method.http && method.http.verb; if (typeof verb === 'string') { verb = verb.toUpperCase(); } if (verb === 'GET' || verb === 'HEAD') { return ACL.READ; } switch (method.name) { case 'create': return ACL.WRITE; case 'updateOrCreate': return ACL.WRITE; case 'upsertWithWhere': return ACL.WRITE; case 'upsert': return ACL.WRITE; case 'exists': return ACL.READ; case 'findById': return ACL.READ; case 'find': return ACL.READ; case 'findOne': return ACL.READ; case 'destroyById': return ACL.WRITE; case 'deleteById': return ACL.WRITE; case 'removeById': return ACL.WRITE; case 'count': return ACL.READ; default: return ACL.EXECUTE; } }
...
if (this.sharedMethod) {
this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]);
} else {
this.methodNames = [];
}
if (this.sharedMethod) {
this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod);
}
this.accessType = context.accessType || AccessContext.ALL;
assert(loopback.AccessToken,
'AccessToken model must be defined before AccessContext model');
this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS;
...
_invalidateAccessTokensOfUsers = function (userIds, options, cb) { if (typeof options === 'function' && cb === undefined) { cb = options; options = {}; } if (!Array.isArray(userIds) || !userIds.length) return process.nextTick(cb); var accessTokenRelation = this.relations.accessTokens; if (!accessTokenRelation) return process.nextTick(cb); var AccessToken = accessTokenRelation.modelTo; var query = {userId: {inq: userIds}}; var tokenPK = AccessToken.definition.idName() || 'id'; if (options.accessToken && tokenPK in options.accessToken) { query[tokenPK] = {neq: options.accessToken[tokenPK]}; } // add principalType in AccessToken.query if using polymorphic relations // between AccessToken and User var relatedUser = AccessToken.relations.user; var isRelationPolymorphic = relatedUser && relatedUser.polymorphic && !relatedUser.modelTo; if (isRelationPolymorphic) { query.principalType = this.modelName; } AccessToken.deleteAll(query, options, cb); }
...
var userIdsToExpire = ctx.hookState.originalUserData.filter(function(u) {
return (newEmail && u.email !== newEmail) ||
(newPassword && u.password !== newPassword);
}).map(function(u) {
return u[pkName];
});
ctx.Model._invalidateAccessTokensOfUsers(userIdsToExpire, ctx.options, next);
});
};
function emailValidator(err, done) {
var value = this.email;
if (value == null)
return;
...
_notifyBaseObservers = function (operation, context, callback) { if (this.base && this.base.notifyObserversOf) this.base.notifyObserversOf(operation, context, callback); else callback(); }
n/a
_runWhenAttachedToApp = function (fn) { if (this.app) return fn(this.app); var self = this; self.once('attached', function() { fn(self.app); }); }
...
ModelCtor,
remotingOptions
);
// before remote hook
ModelCtor.beforeRemote = function(name, fn) {
var className = this.modelName;
this._runWhenAttachedToApp(function(app) {
var remotes = app.remotes();
remotes.before(className + '.' + name, function(ctx, next) {
return fn(ctx, ctx.result, next);
});
});
};
...
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
addListener = function () { [native code] }
n/a
afterRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.after(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
}
}
});
});
} else {
...
afterRemoteError = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.afterError(className + '.' + name, fn); }); }
n/a
attachTo = function (dataSource) { dataSource.attach(this); }
...
this.on('dataSourceAttached', function() {
attachRelatedModels(self);
});
return this.Change;
function attachRelatedModels(self) {
self.Change.attachTo(self.dataSource);
self.Change.getCheckpointModel().attachTo(self.dataSource);
}
};
PersistedModel.rectifyAllChanges = function(callback) {
this.getChangeModel().rectifyAll(callback);
};
...
beforeRemote = function (name, fn) { var className = this.modelName; this._runWhenAttachedToApp(function(app) { var remotes = app.remotes(); remotes.before(className + '.' + name, function(ctx, next) { return fn(ctx, ctx.result, next); }); }); }
...
sharedClass.methods().forEach(function(method) {
var delegateTo = method.rest && method.rest.delegateTo;
if (delegateTo && delegateTo.ctor == relation.modelTo) {
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
var m = method.isStatic ? method.name : 'prototype.' + method.name;
if (before && before[delegateTo.name]) {
self.beforeRemote(m, function(ctx, result, next) {
before[delegateTo.name]._listeners.call(null, ctx, next);
});
}
if (after && after[delegateTo.name]) {
self.afterRemote(m, function(ctx, result, next) {
after[delegateTo.name]._listeners.call(null, ctx, next);
});
...
belongsToRemoting = function (relationName, relation, define) { var modelName = relation.modelTo && relation.modelTo.modelName; modelName = modelName || 'PersistedModel'; var fn = this.prototype[relationName]; var pathName = (relation.options.http && relation.options.http.path) || relationName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], accessType: 'READ', description: format('Fetches belongsTo relation %s.', relationName), returns: {arg: relationName, type: modelName, root: true}, }, fn); }
...
defineRaw(name, options, fn);
};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
...
bulkUpdate = function (updates, options, callback) { var tasks = []; var Model = this; var Change = this.getChangeModel(); var conflicts = []; var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof options === 'function') { options = {}; } options = options || {}; buildLookupOfAffectedModelData(Model, updates, function(err, currentMap) { if (err) return callback(err); updates.forEach(function(update) { var id = update.change.modelId; var current = currentMap[id]; switch (update.type) { case Change.UPDATE: tasks.push(function(cb) { applyUpdate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.CREATE: tasks.push(function(cb) { applyCreate(Model, id, current, update.data, update.change, conflicts, options, cb); }); break; case Change.DELETE: tasks.push(function(cb) { applyDelete(Model, id, current, update.change, conflicts, options, cb); }); break; } }); async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = {conflicts: conflicts}; return callback(err); } callback(); }); }); }
...
function bulkUpdate(_updates, cb) {
debug('\tstarting bulk update');
updates = _updates;
utils.uploadInChunks(
updates,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.bulkUpdate(smallArray, options, function(err) {
// bulk update is a special case where we want to process all chunks and aggregate all errors
chunkCallback(null, err);
});
},
function(notUsed, err) {
var conflicts = err && err.details && err.details.conflicts;
if (conflicts && err.statusCode == 409) {
...
changePassword = function (userId, oldPassword, newPassword, options, cb) { if (cb === undefined && typeof options === 'function') { cb = options; options = undefined; } cb = cb || utils.createPromiseCallback(); // Make sure to use the constructor of the (sub)class // where the method is invoked from (`this` instead of `User`) this.findById(userId, options, (err, inst) => { if (err) return cb(err); if (!inst) { const err = new Error(`User ${userId} not found`); Object.assign(err, { code: 'USER_NOT_FOUND', statusCode: 401, }); return cb(err); } inst.changePassword(oldPassword, newPassword, options, cb); }); return cb.promise; }
...
Object.assign(err, {
code: 'USER_NOT_FOUND',
statusCode: 401,
});
return cb(err);
}
inst.changePassword(oldPassword, newPassword, options, cb);
});
return cb.promise;
};
/**
* Change this user's password (prototype/instance version).
...
changes = function (since, filter, callback) { if (typeof since === 'function') { filter = {}; callback = since; since = -1; } if (typeof filter === 'function') { callback = filter; since = -1; filter = {}; } var idName = this.dataSource.idName(this.modelName); var Change = this.getChangeModel(); var model = this; const changeFilter = this.createChangeFilter(since, filter); filter = filter || {}; filter.fields = {}; filter.where = filter.where || {}; filter.fields[idName] = true; // TODO(ritch) this whole thing could be optimized a bit more Change.find(changeFilter, function(err, changes) { if (err) return callback(err); if (!Array.isArray(changes) || changes.length === 0) return callback(null, []); var ids = changes.map(function(change) { return change.getModelId(); }); filter.where[idName] = {inq: ids}; model.find(filter, function(err, models) { if (err) return callback(err); var modelIds = models.map(function(m) { return m[idName].toString(); }); callback(null, changes.filter(function(ch) { if (ch.type() === Change.DELETE) return true; return modelIds.indexOf(ch.modelId) > -1; })); }); }); }
...
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
function log(err, result) {
if (err) return cb(err);
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
...
checkAccess = function (token, modelId, sharedMethod, ctx, callback) { var ANONYMOUS = registry.getModel('AccessToken').ANONYMOUS; token = token || ANONYMOUS; var aclModel = Model._ACL(); ctx = ctx || {}; if (typeof ctx === 'function' && callback === undefined) { callback = ctx; ctx = {}; } aclModel.checkAccessForContext({ accessToken: token, model: this, property: sharedMethod.name, method: sharedMethod.name, sharedMethod: sharedMethod, modelId: modelId, accessType: this._getAccessTypeForMethod(sharedMethod), remotingContext: ctx, }, function(err, accessRequest) { if (err) return callback(err); callback(null, accessRequest.isAllowed()); }); }
...
var modelSettings = Model.settings || {};
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
if (!req.accessToken) {
errStatusCode = 401;
}
if (Model.checkAccess) {
Model.checkAccess(
req.accessToken,
modelId,
method,
ctx,
function(err, allowed) {
if (err) {
console.log(err);
...
checkpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.bumpLastSeq(cb); }
...
}
cb(err);
});
}
function checkpoints() {
var cb = arguments[arguments.length - 1];
sourceModel.checkpoint(function(err, source) {
if (err) return cb(err);
newSourceCp = source.seq;
targetModel.checkpoint(function(err, target) {
if (err) return cb(err);
newTargetCp = target.seq;
debug('\tcreated checkpoints');
debug('\t\t%s for source model %s', newSourceCp, sourceModel.modelName);
...
clearObservers = function (operation) { if (!(this._observers && this._observers[operation])) return; this._observers[operation].length = 0; }
n/a
confirm = function (uid, token, redirect, fn) { fn = fn || utils.createPromiseCallback(); this.findById(uid, function(err, user) { if (err) { fn(err); } else { if (user && user.verificationToken === token) { user.verificationToken = null; user.emailVerified = true; user.save(function(err) { if (err) { fn(err); } else { fn(); } }); } else { if (user) { err = new Error(g.f('Invalid token: %s', token)); err.statusCode = 400; err.code = 'INVALID_TOKEN'; } else { err = new Error(g.f('User not found: %s', uid)); err.statusCode = 404; err.code = 'USER_NOT_FOUND'; } fn(err); } } }); return fn.promise; }
n/a
count = function (where, cb) { throwNotAttached(this.modelName, 'count'); }
n/a
create = function (data, callback) { throwNotAttached(this.modelName, 'create'); }
...
} else {
var options = {};
if (this.get) {
options = this.get('remoting');
}
return (this._remotes = RemoteObjects.create(options));
}
};
/*!
* Remove a route by reference.
*/
...
createChangeFilter = function (since, modelFilter) { return { where: { checkpoint: {gte: since}, modelName: this.modelName, }, }; }
...
since = -1;
filter = {};
}
var idName = this.dataSource.idName(this.modelName);
var Change = this.getChangeModel();
var model = this;
const changeFilter = this.createChangeFilter(since, filter);
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
...
createChangeStream = function (options, cb) { if (typeof options === 'function') { cb = options; options = undefined; } var idName = this.getIdName(); var Model = this; var changes = new PassThrough({objectMode: true}); var writeable = true; changes.destroy = function() { changes.removeAllListeners('error'); changes.removeAllListeners('end'); writeable = false; changes = null; }; changes.on('error', function() { writeable = false; }); changes.on('end', function() { writeable = false; }); process.nextTick(function() { cb(null, changes); }); Model.observe('after save', createChangeHandler('save')); Model.observe('after delete', createChangeHandler('delete')); function createChangeHandler(type) { return function(ctx, next) { // since it might have set to null via destroy if (!changes) { return next(); } var where = ctx.where; var data = ctx.instance || ctx.data; var whereId = where && where[idName]; // the data includes the id // or the where includes the id var target; if (data && (data[idName] || data[idName] === 0)) { target = data[idName]; } else if (where && (where[idName] || where[idName] === 0)) { target = where[idName]; } var hasTarget = target === 0 || !!target; var change = { target: target, where: where, data: data, }; switch (type) { case 'save': if (ctx.isNewInstance === undefined) { change.type = hasTarget ? 'update' : 'create'; } else { change.type = ctx.isNewInstance ? 'create' : 'update'; } break; case 'delete': change.type = 'remove'; break; } // TODO(ritch) this is ugly... maybe a ReadableStream would be better if (writeable) { changes.write(change); } next(); }; } }
n/a
createOptionsFromRemotingContext = function (ctx) { return { accessToken: ctx.req.accessToken, }; }
...
var EMPTY_OPTIONS = {};
var ModelCtor = ctx.method && ctx.method.ctor;
if (!ModelCtor)
return EMPTY_OPTIONS;
if (typeof ModelCtor.createOptionsFromRemotingContext !== 'function')
return EMPTY_OPTIONS;
debug('createOptionsFromRemotingContext for %s', ctx.method.stringName);
return ModelCtor.createOptionsFromRemotingContext(ctx);
}
/**
* Disable remote invocation for the method with the given name.
*
* @param {String} name The name of the method.
* @param {Boolean} isStatic Is the method static (eg. `MyModel.myMethod`)? Pass
...
createUpdates = function (deltas, cb) { var Change = this.getChangeModel(); var updates = []; var Model = this; var tasks = []; deltas.forEach(function(change) { change = new Change(change); var type = change.type(); var update = {type: type, change: change}; switch (type) { case Change.CREATE: case Change.UPDATE: tasks.push(function(cb) { Model.findById(change.modelId, function(err, inst) { if (err) return cb(err); if (!inst) { return cb && cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); } else { update.data = inst; } updates.push(update); cb(); }); }); break; case Change.DELETE: updates.push(update); break; } }); async.parallel(tasks, function(err) { if (err) return cb(err); cb(null, updates); }); }
...
if (diff && diff.deltas && diff.deltas.length) {
debug('\tbuilding a list of updates');
utils.uploadInChunks(
diff.deltas,
replicationChunkSize,
function(smallArray, chunkCallback) {
return sourceModel.createUpdates(smallArray, chunkCallback);
},
cb);
} else {
// nothing to replicate
done();
}
}
...
currentCheckpoint = function (cb) { var Checkpoint = this.getChangeModel().getCheckpointModel(); Checkpoint.current(cb); }
n/a
defineProperty = function (prop, params) { if (this.dataSource) { this.dataSource.defineProperty(this.modelName, prop, params); } else { this.modelBuilder.defineProperty(this.modelName, prop, params); } }
n/a
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
Model.modelName, id);
debug('\tExpected revision: %s', change.rev);
debug('\tActual revision: %s', rev);
conflicts.push(change);
return Change.rectifyModelChanges(Model.modelName, [id], cb);
}
Model.deleteAll(current.toObject(), options, function(err, result) {
if (err) return cb(err);
var count = result && result.count;
switch (count) {
case 1:
// The happy path, exactly one record was updated
return cb();
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
*/
Conflict.prototype.resolveUsingTarget = function(cb) {
var conflict = this;
conflict.models(function(err, source, target) {
if (err) return done(err);
if (target === null) {
return conflict.SourceModel.deleteById(conflict.modelId, done);
}
var inst = new conflict.SourceModel(
target.toObject(),
{persisted: true});
inst.save(done);
});
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
ctx.Model.find({where: ctx.where, fields: [pkName]}, function(err, list) {
if (err) return next(err);
var ids = list.map(function(u) { return u[pkName]; });
ctx.where = {};
ctx.where[pkName] = {inq: ids};
AccessToken.destroyAll({userId: {inq: ids}}, next);
});
});
/**
* Compare the given `password` with the users hashed password.
*
* @param {String} password The plain text password
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
...
if (!tokenId) {
err = new Error(g.f('{{accessToken}} is required to logout'));
err.status = 401;
process.nextTick(fn, err);
return fn.promise;
}
this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) {
if (err) {
fn(err);
} else if ('count' in info && info.count === 0) {
err = new Error(g.f('Could not find {{accessToken}}'));
err.status = 401;
fn(err);
} else {
...
diff = function (since, remoteChanges, callback) { var Change = this.getChangeModel(); Change.diff(this.modelName, since, remoteChanges, callback); }
...
},
});
};
/**
* Get a set of deltas and conflicts since the given checkpoint.
*
* See [Change.diff()](#change-diff) for details.
*
* @param {Number} since Find deltas since this checkpoint.
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
...
disableRemoteMethod = function (name, isStatic) { deprecated('Model.disableRemoteMethod is deprecated. ' + 'Use Model.disableRemoteMethodByName instead.'); var key = this.sharedClass.getKeyFromMethodNameAndTarget(name, isStatic); this.sharedClass.disableMethodByName(key); this.emit('remoteMethodDisabled', this.sharedClass, key); }
n/a
disableRemoteMethodByName = function (name) { this.sharedClass.disableMethodByName(name); this.emit('remoteMethodDisabled', this.sharedClass, name); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
emit = function () { [native code] }
...
return new Model(data);
});
this.remotes().addClass(Model.sharedClass);
if (Model.settings.trackChanges && Model.Change) {
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
...
enableChangeTracking = function () { var Model = this; var Change = this.Change || this._defineChangeModel(); var cleanupInterval = Model.settings.changeCleanupInterval || 30000; assert(this.dataSource, 'Cannot enableChangeTracking(): ' + this.modelName + ' is not attached to a dataSource'); var idName = this.getIdName(); var idProp = this.definition.properties[idName]; var idType = idProp && idProp.type; var idDefn = idProp && idProp.defaultFn; if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) { deprecated('The model ' + this.modelName + ' is tracking changes, ' + 'which requires a string id with GUID/UUID default value.'); } Model.observe('after save', rectifyOnSave); Model.observe('after delete', rectifyOnDelete); // Only run if the run time is server // Can switch off cleanup by setting the interval to -1 if (runtime.isServer && cleanupInterval > 0) { // initial cleanup cleanup(); // cleanup setInterval(cleanup, cleanupInterval); } function cleanup() { Model.rectifyAllChanges(function(err) { if (err) { Model.handleChangeError(err, 'cleanup'); } }); } }
...
var PersistedModel = this;
// enable change tracking (usually for replication)
if (this.settings.trackChanges) {
PersistedModel._defineChangeModel();
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
...
eventNames = function () { [native code] }
n/a
function exists(id, cb) { throwNotAttached(this.modelName, 'exists'); }
n/a
extend = function (className, subclassProperties, subclassSettings) { var properties = ModelClass.definition.properties; var settings = ModelClass.definition.settings; subclassProperties = subclassProperties || {}; subclassSettings = subclassSettings || {}; // Check if subclass redefines the ids var idFound = false; for (var k in subclassProperties) { if (subclassProperties[k] && subclassProperties[k].id) { idFound = true; break; } } // Merging the properties var keys = Object.keys(properties); for (var i = 0, n = keys.length; i < n; i++) { var key = keys[i]; if (idFound && properties[key].id) { // don't inherit id properties continue; } if (subclassProperties[key] === undefined) { var baseProp = properties[key]; var basePropCopy = baseProp; if (baseProp && typeof baseProp === 'object') { // Deep clone the base prop basePropCopy = mergeSettings(null, baseProp); } subclassProperties[key] = basePropCopy; } } // Merge the settings var originalSubclassSettings = subclassSettings; subclassSettings = mergeSettings(settings, subclassSettings); // Ensure 'base' is not inherited. Note we don't have to delete 'super' // as that is removed from settings by modelBuilder.define and thus // it is never inherited if (!originalSubclassSettings.base) { subclassSettings.base = ModelClass; } // Define the subclass var subClass = modelBuilder.define(className, subclassProperties, subclassSettings, ModelClass); // Calling the setup function if (typeof subClass.setup === 'function') { subClass.setup.call(subClass); } return subClass; }
...
* The base class for **all models**.
*
* **Inheriting from `Model`**
*
* ```js
* var properties = {...};
* var options = {...};
* var MyModel = loopback.Model.extend('MyModel', properties, options);
* ```
*
* **Options**
*
* - `trackChanges` - If true, changes to the model will be tracked. **Required
* for replication.**
*
...
function find(filter, cb) { throwNotAttached(this.modelName, 'find'); }
...
filter = filter || {};
filter.fields = {};
filter.where = filter.where || {};
filter.fields[idName] = true;
// TODO(ritch) this whole thing could be optimized a bit more
Change.find(changeFilter, function(err, changes) {
if (err) return callback(err);
if (!Array.isArray(changes) || changes.length === 0) return callback(null, []);
var ids = changes.map(function(change) {
return change.getModelId();
});
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
...
function findById(id, filter, cb) { throwNotAttached(this.modelName, 'findById'); }
...
var model = new ModelCtor(data);
model.id = id;
fn(null, model);
} else if (data) {
fn(null, new ModelCtor(data));
} else if (id) {
var filter = {};
ModelCtor.findById(id, filter, options, function(err, model) {
if (err) {
fn(err);
} else if (model) {
fn(null, model);
} else {
err = new Error(g.f('could not find a model with apidoc.element.loopback.User.findById %s', id));
err.statusCode = 404;
...
findLastChange = function (id, cb) { var Change = this.getChangeModel(); Change.findOne({where: {modelId: id}}, cb); }
...
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
err = new Error(g.f('No change record found for %s with id %s',
self.modelName, id));
err.statusCode = 404;
return cb(err);
}
...
function findOne(filter, cb) { throwNotAttached(this.modelName, 'findOne'); }
...
PersistedModel.rectifyChange = function(id, callback) {
var Change = this.getChangeModel();
Change.rectifyModelChanges(this.modelName, [id], callback);
};
PersistedModel.findLastChange = function(id, cb) {
var Change = this.getChangeModel();
Change.findOne({where: {modelId: id}}, cb);
};
PersistedModel.updateLastChange = function(id, data, cb) {
var self = this;
this.findLastChange(id, function(err, inst) {
if (err) return cb(err);
if (!inst) {
...
function findOrCreate(query, data, callback) { throwNotAttached(this.modelName, 'findOrCreate'); }
...
cb(err, cp.seq);
});
};
Checkpoint._getSingleton = function(cb) {
var query = {limit: 1}; // match all instances, return only one
var initialData = {seq: 1};
this.findOrCreate(query, initialData, cb);
};
/**
* Increase the current checkpoint if it already exists otherwise initialize it
* @callback {Function} callback
* @param {Error} err
* @param {Object} checkpoint The current checkpoint
...
forEachProperty = function (cb) { var props = ModelClass.definition.properties; var keys = Object.keys(props); for (var i = 0, n = keys.length; i < n; i++) { cb(keys[i], props[keys[i]]); } }
n/a
generateVerificationToken = function (user, cb) { crypto.randomBytes(64, function(err, buf) { cb(err, buf && buf.toString('hex')); }); }
n/a
getApp = function (callback) { var self = this; self._runWhenAttachedToApp(function(app) { assert(self.app); assert.equal(app, self.app); callback(null, app); }); }
n/a
getChangeModel = function () { var changeModel = this.Change; var isSetup = changeModel && changeModel.dataSource; assert(isSetup, 'Cannot get a setup Change model for ' + this.modelName); return changeModel; }
...
* @param {Array} remoteChanges An array of change objects.
* @callback {Function} callback Callback function called with `(err, result)` arguments. Required.
* @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html).
* @param {Object} result Object with `deltas` and `conflicts` properties; see [Change.diff()](#change-diff) for details.
*/
PersistedModel.diff = function(since, remoteChanges, callback) {
var Change = this.getChangeModel();
Change.diff(this.modelName, since, remoteChanges, callback);
};
/**
* Get the changes to a model since the specified checkpoint. Provide a filter object
* to reduce the number of results returned.
* @param {Number} since Return only changes since this checkpoint.
...
getDataSource = function () { return this.dataSource; }
...
* 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 type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
...
getIdName = function () { var Model = this; var ds = Model.getDataSource(); if (ds.idName) { return ds.idName(Model.modelName); } else { return 'id'; } }
...
* Override this method to handle complex IDs.
*
* @param {*} val The `id` value. Will be converted to the type that the `id` property specifies.
*/
PersistedModel.prototype.setId = function(val) {
var ds = this.getDataSource();
this[this.getIdName()] = val;
};
/**
* Get the `id` value for the `PersistedModel`.
*
* @returns {*} The `id` value
*/
...
getMaxListeners = function () { [native code] }
n/a
getPropertyType = function (propName) { var prop = this.definition.properties[propName]; if (!prop) { // The property is not part of the definition return null; } if (!prop.type) { throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; }
n/a
getSourceId = function (cb) { var dataSource = this.dataSource; if (!dataSource) { this.once('dataSourceAttached', this.getSourceId.bind(this, cb)); } assert( dataSource.connector.name, 'Model.getSourceId: cannot get id without dataSource.connector.name' ); var id = [dataSource.connector.name, this.modelName].join('-'); cb(null, id); }
n/a
handleChangeError = function (err, operationName) { if (!err) return; this.emit('error', err, operationName); }
...
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
function rectifyOnSave(ctx, next) {
var instance = ctx.instance || ctx.currentInstance;
...
hasManyRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Find a related item by id for %s.', relationName), accessType: 'READ', returns: {arg: 'result', type: toModelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); var updateByIdFunc = this.prototype['__updateById__' + relationName]; define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, {arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: 'result', type: toModelName, root: true}, }, updateByIdFunc); if (relation.modelThrough || relation.type === 'referencesMany') { var modelThrough = relation.modelThrough || relation.modelTo; var accepts = []; if (relation.type === 'hasMany' && relation.modelThrough) { // Restrict: only hasManyThrough relation can have additional properties accepts.push({ arg: 'data', type: 'object', model: modelThrough.modelName, http: {source: 'body'}, }); } var addFunc = this.prototype['__link__' + relationName]; define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, accepts: [{arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}}, ].concat(accepts).concat([ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ]), description: format('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: {arg: relationName, type: modelThrough.modelName, root: true}, }, addFunc); var removeFunc = this.prototype['__unlink__' + relationName]; define('__unlink__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/rel/:fk'}, accepts: [ { arg: 'fk', type: 'any', description: format('Foreign key for %s', relationName), required: true, http: {source: 'path'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // true --> 200 and false --> 404? var existsFunc = this.prototype['__exists__' + relatio ...
...
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
...
hasOneRemoting = function (relationName, relation, define) { var pathName = (relation.options.http && relation.options.http.path) || relationName; var toModelName = relation.modelTo.modelName; define('__get__' + relationName, { isStatic: false, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: {arg: relationName, type: relation.modelTo.modelName, root: true}, rest: {after: convertNullToNotFoundError.bind(null, toModelName)}, }); define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__update__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Update %s of this model.', relationName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, accepts: [ {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }
...
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
...
hashPassword = function (plain) { this.validatePassword(plain); var salt = bcrypt.genSaltSync(this.settings.saltWorkFactor || SALT_WORK_FACTOR); return bcrypt.hashSync(plain, salt); }
...
return;
}
if (plain.indexOf('$2a$') === 0 && plain.length === 60) {
// The password is already hashed. It can be the case
// when the instance is loaded from DB
this.$password = plain;
} else {
this.$password = this.constructor.hashPassword(plain);
}
};
// Make sure emailVerified is not set by creation
UserModel.beforeRemote('create', function(ctx, user, next) {
var body = ctx.req.body;
if (body && body.emailVerified) {
...
isHiddenProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var hiddenProperties = settings && (settings.hiddenProperties || settings.hidden); if (Array.isArray(hiddenProperties)) { // Cache the hidden properties as an object for quick lookup settings.hiddenProperties = {}; for (var i = 0; i < hiddenProperties.length; i++) { settings.hiddenProperties[hiddenProperties[i]] = true; } hiddenProperties = settings.hiddenProperties; } if (hiddenProperties) { return hiddenProperties[propertyName]; } else { return false; } }
n/a
isProtectedProperty = function (propertyName) { var Model = this; var settings = Model.definition && Model.definition.settings; var protectedProperties = settings && (settings.protectedProperties || settings.protected); if (Array.isArray(protectedProperties)) { // Cache the protected properties as an object for quick lookup settings.protectedProperties = {}; for (var i = 0; i < protectedProperties.length; i++) { settings.protectedProperties[protectedProperties[i]] = true; } protectedProperties = settings.protectedProperties; } if (protectedProperties) { return protectedProperties[propertyName]; } else { return false; } }
n/a
listenerCount = function () { [native code] }
n/a
listeners = function () { [native code] }
n/a
login = function (credentials, include, fn) { var self = this; if (typeof include === 'function') { fn = include; include = undefined; } fn = fn || utils.createPromiseCallback(); include = (include || ''); if (Array.isArray(include)) { include = include.map(function(val) { return val.toLowerCase(); }); } else { include = include.toLowerCase(); } var realmDelimiter; // Check if realm is required var realmRequired = !!(self.settings.realmRequired || self.settings.realmDelimiter); if (realmRequired) { realmDelimiter = self.settings.realmDelimiter; } var query = self.normalizeCredentials(credentials, realmRequired, realmDelimiter); if (realmRequired && !query.realm) { var err1 = new Error(g.f('{{realm}} is required')); err1.statusCode = 400; err1.code = 'REALM_REQUIRED'; fn(err1); return fn.promise; } if (!query.email && !query.username) { var err2 = new Error(g.f('{{username}} or {{email}} is required')); err2.statusCode = 400; err2.code = 'USERNAME_EMAIL_REQUIRED'; fn(err2); return fn.promise; } self.findOne({where: query}, function(err, user) { var defaultError = new Error(g.f('login failed')); defaultError.statusCode = 401; defaultError.code = 'LOGIN_FAILED'; function tokenHandler(err, token) { if (err) return fn(err); if (Array.isArray(include) ? include.indexOf('user') !== -1 : include === 'user') { // NOTE(bajtos) We can't set token.user here: // 1. token.user already exists, it's a function injected by // "AccessToken belongsTo User" relation // 2. ModelBaseClass.toJSON() ignores own properties, thus // the value won't be included in the HTTP response // See also loopback#161 and loopback#162 token.__data.user = user; } fn(err, token); } if (err) { debug('An error is reported from User.findOne: %j', err); fn(defaultError); } else if (user) { user.hasPassword(credentials.password, function(err, isMatch) { if (err) { debug('An error is reported from User.hasPassword: %j', err); fn(defaultError); } else if (isMatch) { if (self.settings.emailVerificationRequired && !user.emailVerified) { // Fail to log in if email verification is not done yet debug('User email has not been verified'); err = new Error(g.f('login failed as the email has not been verified')); err.statusCode = 401; err.code = 'LOGIN_FAILED_EMAIL_NOT_VERIFIED'; fn(err); } else { if (user.createAccessToken.length === 2) { user.createAccessToken(credentials.ttl, tokenHandler); } else { user.createAccessToken(credentials.ttl, credentials, tokenHandler); } } } else { debug('The password is invalid for user %s', query.email || query.username); fn(defaultError); } }); } else { debug('No matching record is found for user %s', query.email || query.username); fn(defaultError); } }); return fn.promise; }
...
* @property {String} email Must be valid email.
* @property {Boolean} emailVerified Set when a user's email has been verified via `confirm()`.
* @property {String} verificationToken Set when `verify()` is called.
* @property {String} realm The namespace the user belongs to. See [Partitioning users with realms](http://loopback.io/doc/en/lb2
/Partitioning-users-with-realms.html) for details.
* @property {Object} settings Extends the `Model.settings` object.
* @property {Boolean} settings.emailVerificationRequired Require the email verification
* process before allowing a login.
* @property {Number} settings.ttl Default time to live (in seconds) for the `AccessToken` created by `User.login() / user.createAccessToken()`.
* Default is `1209600` (2 weeks)
* @property {Number} settings.maxTTL The max value a user can request a token to be alive / valid for.
* Default is `31556926` (1 year)
* @property {Boolean} settings.realmRequired Require a realm when logging in a user.
* @property {String} settings.realmDelimiter When set a realm is required.
* @property {Number} settings.resetPasswordTokenTTL Time to live for password reset `AccessToken`. Default is `900` (15 minutes).
* @property {Number} settings.saltWorkFactor The `bcrypt` salt work factor. Default is `10`.
...
logout = function (tokenId, fn) { fn = fn || utils.createPromiseCallback(); var err; if (!tokenId) { err = new Error(g.f('{{accessToken}} is required to logout')); err.status = 401; process.nextTick(fn, err); return fn.promise; } this.relations.accessTokens.modelTo.destroyById(tokenId, function(err, info) { if (err) { fn(err); } else if ('count' in info && info.count === 0) { err = new Error(g.f('Could not find {{accessToken}}')); err.status = 401; fn(err); } else { fn(); } }); return fn.promise; }
...
return fn.promise;
};
/**
* Logout a user with the given accessToken id.
*
* ```js
* User.logout('asd0a9f8dsj9s0s3223mk', function (err) {
* console.log(err || 'Logged out');
* });
* ```
*
* @param {String} accessTokenID
* @callback {Function} callback
* @param {Error} err
...
mixin = function (anotherClass, options) { if (typeof anotherClass === 'string') { this.modelBuilder.mixins.applyMixin(this, anotherClass, options); } else { if (anotherClass.prototype instanceof ModelBaseClass) { var props = anotherClass.definition.properties; for (var i in props) { if (this.definition.properties[i]) { continue; } this.defineProperty(i, props[i]); } } return jutil.mixin(this, anotherClass, options); } }
n/a
nestRemoting = function (relationName, options, filterCallback) { if (typeof options === 'function' && !filterCallback) { filterCallback = options; options = {}; } options = options || {}; var regExp = /^__([^_]+)__([^_]+)$/; var relation = this.relations[relationName]; if (relation && relation.modelTo && relation.modelTo.sharedClass) { var self = this; var sharedClass = this.sharedClass; var sharedToClass = relation.modelTo.sharedClass; var toModelName = relation.modelTo.modelName; var pathName = options.pathName || relation.options.path || relationName; var paramName = options.paramName || 'nk'; var http = [].concat(sharedToClass.http || [])[0]; var httpPath, acceptArgs; if (relation.multiple) { httpPath = pathName + '/:' + paramName; acceptArgs = [ { arg: paramName, type: 'any', http: {source: 'path'}, description: format('Foreign key for %s.', relation.name), required: true, }, ]; } else { httpPath = pathName; acceptArgs = []; } if (httpPath[0] !== '/') { httpPath = '/' + httpPath; } // A method should return the method name to use, if it is to be // included as a nested method - a falsy return value will skip. var filter = filterCallback || options.filterMethod || function(method, relation) { var matches = method.name.match(regExp); if (matches) { return '__' + matches[1] + '__' + relation.name + '__' + matches[2]; } }; sharedToClass.methods().forEach(function(method) { var methodName; if (!method.isStatic && (methodName = filter(method, relation))) { var prefix = relation.multiple ? '__findById__' : '__get__'; var getterName = options.getterName || (prefix + relationName); var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; opts.accepts = acceptArgs.concat(method.accepts || []); opts.returns = [].concat(method.returns || []); opts.description = method.description; opts.accessType = method.accessType; opts.rest = extend({}, method.rest || {}); opts.rest.delegateTo = method; opts.http = []; var routes = [].concat(method.http || []); routes.forEach(function(route) { if (route.path) { var copy = extend({}, route); copy.path = httpPath + route.path; opts.http.push(copy); } }); if (relation.multiple) { sharedClass.defineMethod(methodName, opts, function(fkId) { var args = Array.prototype.slice.call(arguments, 1); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](fkId, function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return cb(err); } } else if (cb) { cb(err, null); } }); }, method.isStatic); } else { sharedClass.defineMethod(methodName, opts, function() { var args = Array.prototype.slice.call(arguments); var last = args[args.length - 1]; var cb = typeof last === 'function' ? last : null; this[getterName](function(err, inst) { if (err && cb) return cb(err); if (inst instanceof relation.modelTo) { try { nestedFn.apply(inst, args); } catch (err) { if (cb) return ...
n/a
normalizeCredentials = function (credentials, realmRequired, realmDelimiter) { var query = {}; credentials = credentials || {}; if (!realmRequired) { if (credentials.email) { query.email = credentials.email; } else if (credentials.username) { query.username = credentials.username; } } else { if (credentials.realm) { query.realm = credentials.realm; } var parts; if (credentials.email) { parts = splitPrincipal(credentials.email, realmDelimiter); query.email = parts[1]; if (parts[0]) { query.realm = parts[0]; } } else if (credentials.username) { parts = splitPrincipal(credentials.username, realmDelimiter); query.username = parts[1]; if (parts[0]) { query.realm = parts[0]; } } } return query; }
...
var realmDelimiter;
// Check if realm is required
var realmRequired = !!(self.settings.realmRequired ||
self.settings.realmDelimiter);
if (realmRequired) {
realmDelimiter = self.settings.realmDelimiter;
}
var query = self.normalizeCredentials(credentials, realmRequired,
realmDelimiter);
if (realmRequired && !query.realm) {
var err1 = new Error(g.f('{{realm}} is required'));
err1.statusCode = 400;
err1.code = 'REALM_REQUIRED';
fn(err1);
...
notifyObserversAround = function (operation, context, fn, callback) { var self = this; context = context || {}; // Add callback to the context object so that an observer can skip other // ones by calling the callback function directly and not calling next if (context.end === undefined) { context.end = callback; } // First notify before observers return self.notifyObserversOf('before ' + operation, context, function(err, context) { if (err) return callback(err); function cbForWork(err) { var args = [].slice.call(arguments, 0); if (err) return callback.apply(null, args); // Find the list of params from the callback in addition to err var returnedArgs = args.slice(1); // Set up the array of results context.results = returnedArgs; // Notify after observers self.notifyObserversOf('after ' + operation, context, function(err, context) { if (err) return callback(err, context); var results = returnedArgs; if (context && Array.isArray(context.results)) { // Pickup the results from context results = context.results; } // Build the list of params for final callback var args = [err].concat(results); callback.apply(null, args); }); } if (fn.length === 1) { // fn(done) fn(cbForWork); } else { // fn(context, done) fn(context, cbForWork); } }); }
n/a
notifyObserversOf = function (operation, context, callback) { var self = this; if (!callback) callback = utils.createPromiseCallback(); function createNotifier(op) { return function(ctx, done) { if (typeof ctx === 'function' && done === undefined) { done = ctx; ctx = context; } self.notifyObserversOf(op, context, done); }; } if (Array.isArray(operation)) { var tasks = []; for (var i = 0, n = operation.length; i < n; i++) { tasks.push(createNotifier(operation[i])); } return async.waterfall(tasks, callback); } var observers = this._observers && this._observers[operation]; this._notifyBaseObservers(operation, context, function doNotify(err) { if (err) return callback(err, context); if (!observers || !observers.length) return callback(null, context); async.eachSeries( observers, function notifySingleObserver(fn, next) { var retval = fn(context, next); if (retval && typeof retval.then === 'function') { retval.then( function() { next(); return null; }, next // error handler ); } }, function(err) { callback(err, context); } ); }); return callback.promise; }
n/a
observe = function (operation, listener) { this._observers = this._observers || {}; if (!this._observers[operation]) { this._observers[operation] = []; } this._observers[operation].push(listener); }
...
var idType = idProp && idProp.type;
var idDefn = idProp && idProp.defaultFn;
if (idType !== String || !(idDefn === 'uuid' || idDefn === 'guid')) {
deprecated('The model ' + this.modelName + ' is tracking changes, ' +
'which requires a string id with GUID/UUID default value.');
}
Model.observe('after save', rectifyOnSave);
Model.observe('after delete', rectifyOnDelete);
// Only run if the run time is server
// Can switch off cleanup by setting the interval to -1
if (runtime.isServer && cleanupInterval > 0) {
// initial cleanup
...
on = function () { [native code] }
...
this.remotes().addClass(Model.Change.sharedClass);
}
clearHandlerCache(this);
this.emit('modelRemoted', Model.sharedClass);
}
var self = this;
Model.on('remoteMethodDisabled', function(model, methodName) {
self.emit('remoteMethodDisabled', model, methodName);
});
Model.on('remoteMethodAdded', function(model) {
self.emit('remoteMethodAdded', model);
});
Model.shared = isPublic;
...
once = function () { [native code] }
...
remotes.afterError(className + '.' + name, fn);
});
};
ModelCtor._runWhenAttachedToApp = function(fn) {
if (this.app) return fn(this.app);
var self = this;
self.once('attached', function() {
fn(self.app);
});
};
if ('injectOptionsFromRemoteContext' in options) {
console.warn(g.f(
'%s is using model setting %s which is no longer available.',
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
n/a
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
prependListener = function () { [native code] }
n/a
prependOnceListener = function () { [native code] }
n/a
rectifyAllChanges = function (callback) { this.getChangeModel().rectifyAll(callback); }
...
cleanup();
// cleanup
setInterval(cleanup, cleanupInterval);
}
function cleanup() {
Model.rectifyAllChanges(function(err) {
if (err) {
Model.handleChangeError(err, 'cleanup');
}
});
}
};
...
rectifyChange = function (id, callback) { var Change = this.getChangeModel(); Change.rectifyModelChanges(this.modelName, [id], callback); }
...
debug('rectifyOnSave %s -> ' + (id ? 'id %j' : '%s'),
ctx.Model.modelName, id ? id : 'ALL');
debug('context instance:%j currentInstance:%j where:%j data %j',
ctx.instance, ctx.currentInstance, ctx.where, ctx.data);
}
if (id) {
ctx.Model.rectifyChange(id, reportErrorAndNext);
} else {
ctx.Model.rectifyAllChanges(reportErrorAndNext);
}
function reportErrorAndNext(err) {
if (err) {
ctx.Model.handleChangeError(err, 'after save');
...
registerProperty = function (propertyName) { var properties = modelDefinition.build(); var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined; ModelClass.validatesPresenceOf(propertyName, requiredOptions); } Object.defineProperty(ModelClass.prototype, propertyName, { get: function() { if (ModelClass.getter[propertyName]) { return ModelClass.getter[propertyName].call(this); // Try getter first } else { return this.__data && this.__data[propertyName]; // Try __data } }, set: function(value) { var DataType = ModelClass.definition.properties[propertyName].type; if (Array.isArray(DataType) || DataType === Array) { DataType = List; } else if (DataType === Date) { DataType = DateType; } else if (DataType === Boolean) { DataType = BooleanType; } else if (typeof DataType === 'string') { DataType = modelBuilder.resolveType(DataType); } var persistUndefinedAsNull = ModelClass.definition.settings.persistUndefinedAsNull; if (value === undefined && persistUndefinedAsNull) { value = null; } if (ModelClass.setter[propertyName]) { ModelClass.setter[propertyName].call(this, value); // Try setter first } else { this.__data = this.__data || {}; if (value === null || value === undefined) { this.__data[propertyName] = value; } else { if (DataType === List) { this.__data[propertyName] = DataType(value, properties[propertyName].type, this.__data); } else { // Assume the type constructor handles Constructor() call // If not, we should call new DataType(value).valueOf(); this.__data[propertyName] = (value instanceof DataType) ? value : DataType(value); } } } }, configurable: true, enumerable: true, }); // FIXME: [rfeng] Do we need to keep the raw data? // Use $ as the prefix to avoid conflicts with properties such as _id Object.defineProperty(ModelClass.prototype, '$' + propertyName, { get: function() { return this.__data && this.__data[propertyName]; }, set: function(value) { if (!this.__data) { this.__data = {}; } this.__data[propertyName] = value; }, configurable: true, enumerable: false, }); }
n/a
remoteMethod = function (name, options) { if (options.isStatic === undefined) { var m = name.match(/^prototype\.(.*)$/); options.isStatic = !m; name = options.isStatic ? name : m[1]; } if (options.accepts) { options = extend({}, options); options.accepts = setupOptionsArgs(options.accepts); } this.sharedClass.defineMethod(name, options); this.emit('remoteMethodAdded', this.sharedClass); }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function destroyAll(where, cb) { throwNotAttached(this.modelName, 'destroyAll'); }
...
debug('update checkpoint to', checkpoint);
change.checkpoint = checkpoint;
}
if (change.prev === Change.UNKNOWN) {
// this occurs when a record of a change doesn't exist
// and its current revision is null (not found)
change.remove(cb);
} else {
change.save(cb);
}
}
};
/**
...
removeAllListeners = function () { [native code] }
...
var idName = this.getIdName();
var Model = this;
var changes = new PassThrough({objectMode: true});
var writeable = true;
changes.destroy = function() {
changes.removeAllListeners('error');
changes.removeAllListeners('end');
writeable = false;
changes = null;
};
changes.on('error', function() {
writeable = false;
...
function deleteById(id, cb) { throwNotAttached(this.modelName, 'deleteById'); }
n/a
removeListener = function () { [native code] }
n/a
removeObserver = function (operation, listener) { if (!(this._observers && this._observers[operation])) return; var index = this._observers[operation].indexOf(listener); if (index !== -1) { return this._observers[operation].splice(index, 1); } }
n/a
function replaceById(id, data, cb) { throwNotAttached(this.modelName, 'replaceById'); }
n/a
function replaceOrCreate(data, callback) { throwNotAttached(this.modelName, 'replaceOrCreate'); }
n/a
replicate = function (since, targetModel, options, callback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function' && arguments.length > 1) { callback = lastArg; } if (typeof since === 'function' && since.modelName) { targetModel = since; since = -1; } if (typeof since !== 'object') { since = {source: since, target: since}; } if (typeof options === 'function') { options = {}; } options = options || {}; var sourceModel = this; callback = callback || utils.createPromiseCallback(); debug('replicating %s since %s to %s since %s', sourceModel.modelName, since.source, targetModel.modelName, since.target); if (options.filter) { debug('\twith filter %j', options.filter); } // In order to avoid a race condition between the replication and // other clients modifying the data, we must create the new target // checkpoint as the first step of the replication process. // As a side-effect of that, the replicated changes are associated // with the new target checkpoint. This is actually desired behaviour, // because that way clients replicating *from* the target model // since the new checkpoint will pick these changes up. // However, it increases the likelihood of (false) conflicts being detected. // In order to prevent that, we run the replication multiple times, // until no changes were replicated, but at most MAX_ATTEMPTS times // to prevent starvation. In most cases, the second run will find no changes // to replicate and we are done. var MAX_ATTEMPTS = 3; run(1, since); return callback.promise; function run(attempt, since) { debug('\titeration #%s', attempt); tryReplicate(sourceModel, targetModel, since, options, next); function next(err, conflicts, cps, updates) { var finished = err || conflicts.length || !updates || updates.length === 0 || attempt >= MAX_ATTEMPTS; if (finished) return callback(err, conflicts, cps); run(attempt + 1, cps); } } }
n/a
resetPassword = function (options, cb) { cb = cb || utils.createPromiseCallback(); var UserModel = this; var ttl = UserModel.settings.resetPasswordTokenTTL || DEFAULT_RESET_PW_TTL; options = options || {}; if (typeof options.email !== 'string') { var err = new Error(g.f('Email is required')); err.statusCode = 400; err.code = 'EMAIL_REQUIRED'; cb(err); return cb.promise; } try { if (options.password) { UserModel.validatePassword(options.password); } } catch (err) { return cb(err); } var where = { email: options.email, }; if (options.realm) { where.realm = options.realm; } UserModel.findOne({where: where}, function(err, user) { if (err) { return cb(err); } if (!user) { err = new Error(g.f('Email not found')); err.statusCode = 404; err.code = 'EMAIL_NOT_FOUND'; return cb(err); } // create a short lived access token for temp login to change password // TODO(ritch) - eventually this should only allow password change if (UserModel.settings.emailVerificationRequired && !user.emailVerified) { err = new Error(g.f('Email has not been verified')); err.statusCode = 401; err.code = 'RESET_FAILED_EMAIL_NOT_VERIFIED'; return cb(err); } user.createAccessToken(ttl, function(err, accessToken) { if (err) { return cb(err); } cb(); UserModel.emit('resetPasswordRequest', { email: options.email, accessToken: accessToken, user: user, options: options, }); }); }); return cb.promise; }
n/a
scopeRemoting = function (scopeName, scope, define) { var pathName = (scope.options && scope.options.http && scope.options.http.path) || scopeName; var isStatic = scope.isStatic; var toModelName = scope.modelTo.modelName; // https://github.com/strongloop/loopback/issues/811 // Check if the scope is for a hasMany relation var relation = this.relations[scopeName]; if (relation && relation.modelTo) { // For a relation with through model, the toModelName should be the one // from the target model toModelName = relation.modelTo.modelName; } define('__get__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName}, accepts: [ {arg: 'filter', type: 'object'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: scopeName, type: [toModelName], root: true}, }); define('__create__' + scopeName, { isStatic: isStatic, http: {verb: 'post', path: '/' + pathName}, accepts: [ { arg: 'data', type: 'object', allowArray: true, model: toModelName, http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: {arg: 'data', type: toModelName, root: true}, }); define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, accepts: [ { arg: 'where', type: 'object', // The "where" argument is not exposed in the REST API // but we need to provide a value so that we can pass "options" // as the third argument. http: function(ctx) { return undefined; }, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); define('__count__' + scopeName, { isStatic: isStatic, http: {verb: 'get', path: '/' + pathName + '/count'}, accepts: [ { arg: 'where', type: 'object', description: 'Criteria to match model instances', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], description: format('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'}, }); }
...
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
});
return ModelCtor;
};
/*!
...
setMaxListeners = function () { [native code] }
n/a
setup = function () { // We need to call the base class's setup method User.base.setup.call(this); var UserModel = this; // max ttl this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL; this.settings.ttl = this.settings.ttl || DEFAULT_TTL; UserModel.setter.email = function(value) { if (!UserModel.settings.caseSensitiveEmail) { this.$email = value.toLowerCase(); } else { this.$email = value; } }; UserModel.setter.password = function(plain) { if (typeof plain !== 'string') { return; } if (plain.indexOf('$2a$') === 0 && plain.length === 60) { // The password is already hashed. It can be the case // when the instance is loaded from DB this.$password = plain; } else { this.$password = this.constructor.hashPassword(plain); } }; // Make sure emailVerified is not set by creation UserModel.beforeRemote('create', function(ctx, user, next) { var body = ctx.req.body; if (body && body.emailVerified) { body.emailVerified = false; } next(); }); UserModel.remoteMethod( 'login', { description: 'Login a user with username/email and password.', accepts: [ {arg: 'credentials', type: 'object', required: true, http: {source: 'body'}}, {arg: 'include', type: ['string'], http: {source: 'query'}, description: 'Related objects to include in the response. ' + 'See the description of return value for more details.'}, ], returns: { arg: 'accessToken', type: 'object', root: true, description: g.f('The response body contains properties of the {{AccessToken}} created on login.\n' + 'Depending on the value of `include` parameter, the body may contain ' + 'additional properties:\n\n' + ' - `user` - `U+007BUserU+007D` - Data of the currently logged in user. ' + '{{(`include=user`)}}\n\n'), }, http: {verb: 'post'}, } ); UserModel.remoteMethod( 'logout', { description: 'Logout a user with access token.', accepts: [ {arg: 'access_token', type: 'string', http: function(ctx) { var req = ctx && ctx.req; var accessToken = req && req.accessToken; var tokenID = accessToken ? accessToken.id : undefined; return tokenID; }, description: 'Do not supply this argument, it is automatically extracted ' + 'from request headers.', }, ], http: {verb: 'all'}, } ); UserModel.remoteMethod( 'confirm', { description: 'Confirm a user registration with email verification token.', accepts: [ {arg: 'uid', type: 'string', required: true}, {arg: 'token', type: 'string', required: true}, {arg: 'redirect', type: 'string'}, ], http: {verb: 'get', path: '/confirm'}, } ); UserModel.remoteMethod( 'resetPassword', { description: 'Reset password for a user with email.', accepts: [ {arg: 'options', type: 'object', required: true, http: {source: 'body'}}, ], http: {verb: 'post', path: '/reset'}, } ); UserModel.remoteMethod( 'changePassword', { description: 'Change a user\'s password.', accepts: [ {arg: 'id', type: 'any', http: ctx => ctx.req.accessToken && ctx.req.accessToken.userId, }, {arg: 'oldPassword', type: 'string', required: true, http: {source: 'form'}}, {arg: 'newPassword', type: 'string', required: true, http: {source: 'form'}}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], http: {verb: 'POST', path: '/change-password'}, } ); UserModel.afterRemote('confirm', function(ctx, inst, next) { if (ctx.args.redirect !== undefined) { if (!ctx.res) { return next(new Error(g.f('The transport does not support HTTP redirects.'))); } ctx.res.location(ctx.args.redirect); ctx.res.status(302); } next(); }); // default models assert(loopb ...
...
Model.createOptionsFromRemotingContext = function(ctx) {
return {
accessToken: ctx.req.accessToken,
};
};
// setup the initial model
Model.setup();
return Model;
};
...
setupRemoting = function () { var PersistedModel = this; var typeName = PersistedModel.modelName; var options = PersistedModel.settings; // This is just for LB 3.x options.replaceOnPUT = options.replaceOnPUT !== false; function setRemoting(scope, name, options) { var fn = scope[name]; fn._delegate = true; options.isStatic = scope === PersistedModel; PersistedModel.remoteMethod(name, options); } setRemoting(PersistedModel, 'create', { description: 'Create a new instance of the model and persist it into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, allowArray: true, description: 'Model instance data', http: {source: 'body'}, }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/'}, }); var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], description: 'Patch an existing model instance or insert a new one ' + 'into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'patch', path: '/'}], }; if (!options.replaceOnPUT) { upsertOptions.http.unshift({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'patchOrCreate', upsertOptions); var replaceOrCreateOptions = { description: 'Replace an existing model instance or insert a new one into the data source.', accessType: 'WRITE', accepts: [ { arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'Model instance data', }, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: [{verb: 'post', path: '/replaceOrCreate'}], }; if (options.replaceOnPUT) { replaceOrCreateOptions.http.push({verb: 'put', path: '/'}); } setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'upsertWithWhere', { aliases: ['patchOrCreateWithWhere'], description: 'Update an existing model instance or insert a new one into ' + 'the data source based on the where criteria.', accessType: 'WRITE', accepts: [ {arg: 'where', type: 'object', http: {source: 'query'}, description: 'Criteria to match model instances'}, {arg: 'data', type: 'object', model: typeName, http: {source: 'body'}, description: 'An object of model property name/value pairs'}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'data', type: typeName, root: true}, http: {verb: 'post', path: '/upsertWithWhere'}, }); setRemoting(PersistedModel, 'exists', { description: 'Check whether a model instance exists in the data source.', accessType: 'READ', accepts: [ {arg: 'id', type: 'any', description: 'Model id', required: true}, {arg: 'options', type: 'object', http: 'optionsFromRequest'}, ], returns: {arg: 'exists', type: 'boolean'}, http: [ {verb: 'get', path: '/:id/exists'}, {verb: 'head', path: '/:id'}, ], rest: { // After hook to map exists to 200/404 for HEAD after: function(ctx, cb) { if (ctx.req.method === 'GET') { // For GET, return {exists: true|false} as is return cb(); } if (!ctx.result.exists) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); var msg = 'Unknown "' + modelName + '" id "' + id + '".'; var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; cb(error); } else { cb(); } ...
...
PersistedModel.once('dataSourceAttached', function() {
PersistedModel.enableChangeTracking();
});
} else if (this.settings.enableRemoteReplication) {
PersistedModel._defineChangeModel();
}
PersistedModel.setupRemoting();
};
/*!
* Throw an error telling the user that the method is not available and why.
*/
function throwNotAttached(modelName, methodName) {
...
sharedCtor = function (data, id, options, fn) { var ModelCtor = this; var isRemoteInvocationWithOptions = typeof data !== 'object' && typeof id === 'object' && typeof options === 'function'; if (isRemoteInvocationWithOptions) { // sharedCtor(id, options, fn) fn = options; options = id; id = data; data = null; } else if (typeof data === 'function') { // sharedCtor(fn) fn = data; data = null; id = null; options = null; } else if (typeof id === 'function') { // sharedCtor(data, fn) // sharedCtor(id, fn) fn = id; options = null; if (typeof data !== 'object') { id = data; data = null; } else { id = null; } } if (id && data) { var model = new ModelCtor(data); model.id = id; fn(null, model); } else if (data) { fn(null, new ModelCtor(data)); } else if (id) { var filter = {}; ModelCtor.findById(id, filter, options, function(err, model) { if (err) { fn(err); } else if (model) { fn(null, model); } else { err = new Error(g.f('could not find a model with apidoc.element.loopback.User.sharedCtor %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { fn(new Error(g.f('must specify an apidoc.element.loopback.User.sharedCtor or {{data}}'))); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
* @param {String} str The string to be hashed
* @return {String} The hashed string
*/
Change.hash = function(str) {
return crypto
.createHash(Change.settings.hashAlgorithm || 'sha1')
.update(str)
.digest('hex');
};
/**
* Get the revision string for the given object
* @param {Object} inst The data to get the revision string for
* @return {String} The revision string
...
function updateAll(where, data, cb) { throwNotAttached(this.modelName, 'updateAll'); }
...
/**
* 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'}, ...}
...
updateLastChange = function (id, data, cb) { var self = this; this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { err = new Error(g.f('No change record found for %s with id %s', self.modelName, id)); err.statusCode = 404; return cb(err); } inst.updateAttributes(data, cb); }); }
...
Conflict.prototype.resolve = function(cb) {
var conflict = this;
conflict.TargetModel.findLastChange(
this.modelId,
function(err, targetChange) {
if (err) return cb(err);
conflict.SourceModel.updateLastChange(
conflict.modelId,
{prev: targetChange.rev},
cb);
});
};
/**
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
} else {
var ch = new Change({
id: id,
modelName: modelName,
modelId: modelId,
});
ch.debug('creating change');
Change.updateOrCreate(ch, callback);
}
});
return callback.promise;
};
/**
* Update (or create) the change with the current revision.
...
function upsert(data, callback) { throwNotAttached(this.modelName, 'upsert'); }
...
}
});
// then save
function save() {
inst.trigger('save', function(saveDone) {
inst.trigger('update', function(updateDone) {
Model.upsert(inst, function(err) {
inst._initProperties(data);
updateDone.call(inst, function() {
saveDone.call(inst, function() {
callback(err, inst);
});
});
});
...
function upsertWithWhere(where, data, callback) { throwNotAttached(this.modelName, 'upsertWithWhere'); }
n/a
validate = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
var id = tokenIdForRequest(req, options);
if (id) {
this.findById(id, function(err, token) {
if (err) {
cb(err);
} else if (token) {
token.validate(function(err, isValid) {
if (err) {
cb(err);
} else if (isValid) {
cb(null, token);
} else {
var e = new Error(g.f('Invalid Access Token'));
e.status = e.statusCode = 401;
...
validateAsync = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatePassword = function (plain) { var err; if (plain && typeof plain === 'string' && plain.length <= MAX_PASSWORD_LENGTH) { return true; } if (plain.length > MAX_PASSWORD_LENGTH) { err = new Error(g.f('Password too long: %s', plain)); err.code = 'PASSWORD_TOO_LONG'; } else { err = new Error(g.f('Invalid password: %s', plain)); err.code = 'INVALID_PASSWORD'; } err.statusCode = 422; throw err; }
...
code: 'INVALID_PASSWORD',
statusCode: 400,
});
return cb(err);
}
try {
User.validatePassword(newPassword);
} catch (err) {
return cb(err);
}
const delta = {password: newPassword};
this.patchAttributes(delta, options, (err, updated) => cb(err));
});
...
validatesAbsenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesExclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesFormatOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesInclusionOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesLengthOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesNumericalityOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesPresenceOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
n/a
validatesUniquenessOf = function () { var args = Array.prototype.slice.call(arguments); args[1] = args[1] || {}; configure(this, name, args, opts); }
...
async.parallel(inRoleTasks, function(err, results) {
debug('getRoles() returns: %j %j', err, roles);
if (callback) callback(err, roles);
});
return callback.promise;
};
Role.validatesUniquenessOf('name', {message: 'already exists'});
};
...
changePassword = function (oldPassword, newPassword, options, cb) { if (cb === undefined && typeof options === 'function') { cb = options; options = undefined; } cb = cb || utils.createPromiseCallback(); this.hasPassword(oldPassword, (err, isMatch) => { if (err) return cb(err); if (!isMatch) { const err = new Error('Invalid current password'); Object.assign(err, { code: 'INVALID_PASSWORD', statusCode: 400, }); return cb(err); } try { User.validatePassword(newPassword); } catch (err) { return cb(err); } const delta = {password: newPassword}; this.patchAttributes(delta, options, (err, updated) => cb(err)); }); return cb.promise; }
...
Object.assign(err, {
code: 'USER_NOT_FOUND',
statusCode: 401,
});
return cb(err);
}
inst.changePassword(oldPassword, newPassword, options, cb);
});
return cb.promise;
};
/**
* Change this user's password (prototype/instance version).
...
createAccessToken = function (ttl, options, cb) { if (cb === undefined && typeof options === 'function') { // createAccessToken(ttl, cb) cb = options; options = undefined; } cb = cb || utils.createPromiseCallback(); if (typeof ttl === 'object' && !options) { // createAccessToken(options, cb) options = ttl; ttl = options.ttl; } options = options || {}; var userModel = this.constructor; ttl = Math.min(ttl || userModel.settings.ttl, userModel.settings.maxTTL); this.accessTokens.create({ ttl: ttl, }, cb); return cb.promise; }
...
* @property {String} email Must be valid email.
* @property {Boolean} emailVerified Set when a user's email has been verified via `confirm()`.
* @property {String} verificationToken Set when `verify()` is called.
* @property {String} realm The namespace the user belongs to. See [Partitioning users with realms](http://loopback.io/doc/en/lb2
/Partitioning-users-with-realms.html) for details.
* @property {Object} settings Extends the `Model.settings` object.
* @property {Boolean} settings.emailVerificationRequired Require the email verification
* process before allowing a login.
* @property {Number} settings.ttl Default time to live (in seconds) for the `AccessToken` created by `User.login() / user.createAccessToken()`.
* Default is `1209600` (2 weeks)
* @property {Number} settings.maxTTL The max value a user can request a token to be alive / valid for.
* Default is `31556926` (1 year)
* @property {Boolean} settings.realmRequired Require a realm when logging in a user.
* @property {String} settings.realmDelimiter When set a realm is required.
* @property {Number} settings.resetPasswordTokenTTL Time to live for password reset `AccessToken`. Default is `900` (15 minutes).
* @property {Number} settings.saltWorkFactor The `bcrypt` salt work factor. Default is `10`.
...
hasPassword = function (plain, fn) { fn = fn || utils.createPromiseCallback(); if (this.password && plain) { bcrypt.compare(plain, this.password, function(err, isMatch) { if (err) return fn(err); fn(null, isMatch); }); } else { fn(null, false); } return fn.promise; }
...
fn(err, token);
}
if (err) {
debug('An error is reported from User.findOne: %j', err);
fn(defaultError);
} else if (user) {
user.hasPassword(credentials.password, function(err, isMatch) {
if (err) {
debug('An error is reported from User.hasPassword: %j', err);
fn(defaultError);
} else if (isMatch) {
if (self.settings.emailVerificationRequired && !user.emailVerified) {
// Fail to log in if email verification is not done yet
debug('User email has not been verified');
...
verify = function (options, fn) { fn = fn || utils.createPromiseCallback(); var user = this; var userModel = this.constructor; var registry = userModel.registry; var pkName = userModel.definition.idName() || 'id'; assert(typeof options === 'object', 'options required when calling user.verify()'); assert(options.type, 'You must supply a verification type (options.type)'); assert(options.type === 'email', 'Unsupported verification type'); assert(options.to || this.email, 'Must include options.to when calling user.verify() ' + 'or the user must have an email property'); assert(options.from, 'Must include options.from when calling user.verify()'); options.redirect = options.redirect || '/'; var defaultTemplate = path.join(__dirname, '..', '..', 'templates', 'verify.ejs'); options.template = path.resolve(options.template || defaultTemplate); options.user = this; options.protocol = options.protocol || 'http'; var app = userModel.app; options.host = options.host || (app && app.get('host')) || 'localhost'; options.port = options.port || (app && app.get('port')) || 3000; options.restApiRoot = options.restApiRoot || (app && app.get('restApiRoot')) || '/api'; var displayPort = ( (options.protocol === 'http' && options.port == '80') || (options.protocol === 'https' && options.port == '443') ) ? '' : ':' + options.port; var urlPath = joinUrlPath( options.restApiRoot, userModel.http.path, userModel.sharedClass.findMethodByName('confirm').http.path ); options.verifyHref = options.verifyHref || options.protocol + '://' + options.host + displayPort + urlPath + '?' + qs.stringify({ uid: '' + options.user[pkName], redirect: options.redirect, }); options.templateFn = options.templateFn || createVerificationEmailBody; // Email model var Email = options.mailer || this.constructor.email || registry.getModelByType(loopback.Email); // Set a default token generation function if one is not provided var tokenGenerator = options.generateVerificationToken || User.generateVerificationToken; assert(typeof tokenGenerator === 'function', 'generateVerificationToken must be a function'); tokenGenerator(user, function(err, token) { if (err) { return fn(err); } user.verificationToken = token; user.save(function(err) { if (err) { fn(err); } else { sendEmail(user); } }); }); // TODO - support more verification types function sendEmail(user) { options.verifyHref += '&token=' + user.verificationToken; options.verificationToken = user.verificationToken; options.text = options.text || g.f('Please verify your email by opening ' + 'this link in a web browser:\n\t%s', options.verifyHref); options.text = options.text.replace(/\{href\}/g, options.verifyHref); options.to = options.to || user.email; options.subject = options.subject || g.f('Thanks for Registering'); options.headers = options.headers || {}; options.templateFn(options, function(err, html) { if (err) { fn(err); } else { setHtmlContentAndSend(html); } }); function setHtmlContentAndSend(html) { options.html = html; // Remove options.template to prevent rejection by certain // nodemailer transport plugins. delete options.template; Email.send(options, function(err, email) { if (err) { fn(err); } else { fn(null, {email: email, token: user.verificationToken, uid: user[pkName]}); } }); } } return fn.promise; }
...
* type: 'email',
* to: user.email,
* template: 'verify.ejs',
* redirect: '/',
* tokenGenerator: function (user, cb) { cb("random-token"); }
* };
*
* user.verify(options, next);
* ```
*
* @options {Object} options
* @property {String} type Must be 'email'.
* @property {String} to Email address to which verification email is sent.
* @property {String} from Sender email addresss, for example
* `'noreply@myapp.com'`.
...
email = function (value) { if (!UserModel.settings.caseSensitiveEmail) { this.$email = value.toLowerCase(); } else { this.$email = value; } }
n/a
password = function (plain) { if (typeof plain !== 'string') { return; } if (plain.indexOf('$2a$') === 0 && plain.length === 60) { // The password is already hashed. It can be the case // when the instance is loaded from DB this.$password = plain; } else { this.$password = this.constructor.hashPassword(plain); } }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
function Error() { [native code] }
n/a
function Error() { [native code] }
n/a
function captureStackTrace() { [native code] }
n/a
function AccessContext(context) { if (!(this instanceof AccessContext)) { return new AccessContext(context); } context = context || {}; assert(context.registry, 'Application registry is mandatory in AccessContext but missing in provided context'); this.registry = context.registry; this.principals = context.principals || []; var model = context.model; model = ('string' === typeof model) ? this.registry.getModel(model) : model; this.model = model; this.modelName = model && model.modelName; this.modelId = context.id || context.modelId; this.property = context.property || AccessContext.ALL; this.method = context.method; this.sharedMethod = context.sharedMethod; this.sharedClass = this.sharedMethod && this.sharedMethod.sharedClass; if (this.sharedMethod) { this.methodNames = this.sharedMethod.aliases.concat([this.sharedMethod.name]); } else { this.methodNames = []; } if (this.sharedMethod) { this.accessType = this.model._getAccessTypeForMethod(this.sharedMethod); } this.accessType = context.accessType || AccessContext.ALL; assert(loopback.AccessToken, 'AccessToken model must be defined before AccessContext model'); this.accessToken = context.accessToken || loopback.AccessToken.ANONYMOUS; var principalType = context.principalType || Principal.USER; var principalId = context.principalId || undefined; var principalName = context.principalName || undefined; if (principalId) { this.addPrincipal(principalType, principalId, principalName); } var token = this.accessToken || {}; if (token.userId) { this.addPrincipal(Principal.USER, token.userId); } if (token.appId) { this.addPrincipal(Principal.APPLICATION, token.appId); } this.remotingContext = context.remotingContext; }
n/a
function AccessRequest(model, property, accessType, permission, methodNames, registry) { if (!(this instanceof AccessRequest)) { return new AccessRequest(model, property, accessType, permission, methodNames); } if (arguments.length === 1 && typeof model === 'object') { // The argument is an object that contains all required properties var obj = model || {}; this.model = obj.model || AccessContext.ALL; this.property = obj.property || AccessContext.ALL; this.accessType = obj.accessType || AccessContext.ALL; this.permission = obj.permission || AccessContext.DEFAULT; this.methodNames = obj.methodNames || []; this.registry = obj.registry; } else { this.model = model || AccessContext.ALL; this.property = property || AccessContext.ALL; this.accessType = accessType || AccessContext.ALL; this.permission = permission || AccessContext.DEFAULT; this.methodNames = methodNames || []; this.registry = registry; } // do not create AccessRequest without a registry assert(this.registry, 'Application registry is mandatory in AccessRequest but missing in provided argument(s)'); }
n/a
function Principal(type, id, name) { if (!(this instanceof Principal)) { return new Principal(type, id, name); } this.type = type; this.id = id; this.name = name; }
n/a
acl = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function all(path) { this.lazyrouter(); var route = this._router.route(path); var args = slice.call(arguments, 1); for (var i = 0; i < methods.length; i++) { route[methods[i]].apply(route, args); } return this; }
n/a
bind = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
...
accepts: [
{arg: 'refresh', type: 'boolean', http: {source: 'query'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
],
description: format('Fetches hasOne relation %s.', relationName),
accessType: 'READ',
returns: {arg: relationName, type: relation.modelTo.modelName, root: true},
rest: {after: convertNullToNotFoundError.bind(null, toModelName)},
});
define('__create__' + relationName, {
isStatic: false,
http: {verb: 'post', path: '/' + pathName},
accepts: [
{
...
checkout = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
connect = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
copy = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function defaultConfiguration() { var env = process.env.NODE_ENV || 'development'; // default settings this.enable('x-powered-by'); this.set('etag', 'weak'); this.set('env', env); this.set('query parser', 'extended'); this.set('subdomain offset', 2); this.set('trust proxy', false); // trust proxy inherit back-compat Object.defineProperty(this.settings, trustProxyDefaultSymbol, { configurable: true, value: true }); debug('booting in %s mode', env); this.on('mount', function onmount(parent) { // inherit trust proxy if (this.settings[trustProxyDefaultSymbol] === true && typeof parent.settings['trust proxy fn'] === 'function') { delete this.settings['trust proxy']; delete this.settings['trust proxy fn']; } // inherit protos setPrototyeOf(this.request, parent.request) setPrototyeOf(this.response, parent.response) setPrototyeOf(this.engines, parent.engines) setPrototyeOf(this.settings, parent.settings) }); // setup locals this.locals = Object.create(null); // top-most app is mounted at / this.mountpath = '/'; // default locals this.locals.settings = this.settings; // default configuration this.set('view', View); this.set('views', resolve('views')); this.set('jsonp callback name', 'callback'); if (env === 'production') { this.enable('view cache'); } Object.defineProperty(this, 'router', { get: function() { throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.'); } }); }
n/a
del = function (arg0) { "use strict" log.call(deprecate, message, site) return fn.apply(this, arguments) }
n/a
delete = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function disable(setting) { return this.set(setting, false); }
n/a
function disabled(setting) { return !this.set(setting); }
n/a
function enable(setting) { return this.set(setting, true); }
n/a
function enabled(setting) { return Boolean(this.set(setting)); }
n/a
function engine(ext, fn) { if (typeof fn !== 'function') { throw new Error('callback function required'); } // get file extension var extension = ext[0] !== '.' ? '.' + ext : ext; // store engine this.engines[extension] = fn; return this; }
n/a
get = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
...
* supports Express middleware. See
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
...
function handle(req, res, callback) { var router = this._router; // final handler var done = callback || finalhandler(req, res, { env: this.get('env'), onerror: logerror.bind(this) }); // no routes if (!router) { debug('no routes defined on app'); done(); return; } router.handle(req, res, done); }
n/a
head = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function init() { this.cache = {}; this.engines = {}; this.settings = {}; this.defaultConfiguration(); }
n/a
function lazyrouter() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled('case sensitive routing'), strict: this.enabled('strict routing') }); this._router.use(query(this.get('query parser fn'))); this._router.use(middleware.init(this)); } }
...
* names to add.
*
* @returns {object} this (fluent API)
*
* @header app.defineMiddlewarePhases(nameOrArray)
*/
proto.defineMiddlewarePhases = function(nameOrArray) {
this.lazyrouter();
if (Array.isArray(nameOrArray)) {
this._requestHandlingPhases =
mergePhaseNameLists(this._requestHandlingPhases, nameOrArray);
} else {
// add the new phase before 'routes'
var routesIx = this._requestHandlingPhases.indexOf('routes');
...
link = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); }
...
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-karma');
grunt.registerTask('e2e-server', function() {
var done = this.async();
var app = require('./test/fixtures/e2e/app');
app.listen(0, function() {
process.env.PORT = this.address().port;
done();
});
});
grunt.registerTask('skip-karma-on-windows', function() {
console.log('*** SKIPPING PHANTOM-JS BASED TESTS ON WINDOWS ***');
...
lock = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
m-search = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
merge = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
mkactivity = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
mkcalendar = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
mkcol = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
move = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
notify = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
options = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function param(name, fn) { this.lazyrouter(); if (Array.isArray(name)) { for (var i = 0; i < name.length; i++) { this.param(name[i], fn); } return this; } this._router.param(name, fn); return this; }
...
remotes.authorization = function(ctx, next) {
var method = ctx.method;
var req = ctx.req;
var Model = method.ctor;
var modelInstance = ctx.instance;
var modelId = modelInstance && modelInstance.id ||
// replacement for deprecated req.param()
(req.params && req.params.id !== undefined ? req.params.id :
req.body && req.body.id !== undefined ? req.body.id :
req.query && req.query.id !== undefined ? req.query.id :
undefined);
var modelName = Model.modelName;
...
patch = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function path() { return this.parent ? this.parent.path() + this.mountpath : ''; }
n/a
post = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
propfind = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
proppatch = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
purge = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
put = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
rebind = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function render(name, options, callback) { var cache = this.cache; var done = callback; var engines = this.engines; var opts = options; var renderOptions = {}; var view; // support callback function as second arg if (typeof options === 'function') { done = options; opts = {}; } // merge app.locals merge(renderOptions, this.locals); // merge options._locals if (opts._locals) { merge(renderOptions, opts._locals); } // merge options merge(renderOptions, opts); // set .cache unless explicitly provided if (renderOptions.cache == null) { renderOptions.cache = this.enabled('view cache'); } // primed cache if (renderOptions.cache) { view = cache[name]; } // view if (!view) { var View = this.get('view'); view = new View(name, { defaultEngine: this.get('view engine'), root: this.get('views'), engines: engines }); if (!view.path) { var dirs = Array.isArray(view.root) && view.root.length > 1 ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"' : 'directory "' + view.root + '"' var err = new Error('Failed to lookup view "' + name + '" in views ' + dirs); err.view = view; return done(err); } // prime the cache if (renderOptions.cache) { cache[name] = view; } } // render tryRender(view, renderOptions, done); }
n/a
report = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function route(path) { this.lazyrouter(); return this._router.route(path); }
n/a
search = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function set(setting, val) { if (arguments.length === 1) { // app.get(setting) return this.settings[setting]; } debug('set "%s" to %o', setting, val); // set value this.settings[setting] = val; // trigger matched settings switch (setting) { case 'etag': this.set('etag fn', compileETag(val)); break; case 'query parser': this.set('query parser fn', compileQueryParser(val)); break; case 'trust proxy': this.set('trust proxy fn', compileTrust(val)); // trust proxy inherit back-compat Object.defineProperty(this.settings, trustProxyDefaultSymbol, { configurable: true, value: false }); break; } return this; }
...
*
* For example, to listen on the specified port and all hosts, and ignore app config.
* ```js
* app.listen(80);
* ```
*
* The function also installs a `listening` callback that calls
* `app.set('port')` with the value returned by `server.address().port`.
* This way the port param contains always the real port number, even when
* listen was called with port number 0.
*
* @param {Function} [cb] If specified, the callback is added as a listener
* for the server's "listening" event.
* @returns {http.Server} A node `http.Server` with this application configured
* as the request handler.
...
subscribe = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
trace = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
unbind = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
unlink = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
unlock = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
unsubscribe = function (path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }
n/a
function use(fn) { var offset = 0; var path = '/'; // default path to '/' // disambiguate app.use([fn]) if (typeof fn !== 'function') { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; } // first arg is the path if (typeof arg !== 'function') { offset = 1; path = fn; } } var fns = flatten(slice.call(arguments, offset)); if (fns.length === 0) { throw new TypeError('app.use() requires middleware functions'); } // setup router this.lazyrouter(); var router = this._router; fns.forEach(function (fn) { // non-express app if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); } debug('.use app under %s', path); fn.mountpath = path; fn.parent = this; // restore .app property on req and res router.use(path, function mounted_app(req, res, next) { var orig = req.app; fn.handle(req, res, function (err) { setPrototyeOf(req, orig.request) setPrototyeOf(res, orig.response) next(err); }); }); // mounted an app fn.emit('mount', this); }, this); return this; }
...
if (this._requestHandlingPhases.indexOf(name) === -1)
throw new Error(g.f('Unknown {{middleware}} phase %s', name));
debug('use %s %s %s', fullPhaseName, paths, handlerName);
this._skipLayerSorting = true;
this.use(paths, handler);
var layer = this._findLayerByHandler(handler);
if (layer) {
// Set the phase name for sorting
layer.phase = fullPhaseName;
} else {
debug('No matching layer is found for %s %s', fullPhaseName, handlerName);
...
function createApplication(options) { var app = loopbackExpress(); merge(app, proto); app.loopback = loopback; // 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 var registry = app.registry = new Registry(); if (options && options.loadBuiltinModels === true) { require('./builtin-models')(registry); } } else { app.registry = loopback.registry; } return app; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function Connector(options) { EventEmitter.apply(this, arguments); this.options = options; debug('created with options', options); }
n/a
function DataSource(name, settings, modelBuilder) { if (!(this instanceof DataSource)) { return new DataSource(name, settings); } // Check if the settings object is passed as the first argument if (typeof name === 'object' && settings === undefined) { settings = name; name = undefined; } // Check if the first argument is a URL if (typeof name === 'string' && name.indexOf('://') !== -1) { name = utils.parseSettings(name); } // Check if the settings is in the form of URL string if (typeof settings === 'string' && settings.indexOf('://') !== -1) { settings = utils.parseSettings(settings); } this.modelBuilder = modelBuilder || new ModelBuilder(); this.models = this.modelBuilder.models; this.definitions = this.modelBuilder.definitions; this.juggler = juggler; // operation metadata // Initialize it before calling setup as the connector might register operations this._operations = {}; this.setup(name, settings); this._setupConnector(); // connector var connector = this.connector; // DataAccessObject - connector defined or supply the default var dao = (connector && connector.DataAccessObject) || this.constructor.DataAccessObject; this.DataAccessObject = function() { }; // define DataAccessObject methods Object.keys(dao).forEach(function(name) { var fn = dao[name]; this.DataAccessObject[name] = fn; if (typeof fn === 'function') { this.defineOperation(name, { accepts: fn.accepts, 'returns': fn.returns, http: fn.http, remoteEnabled: fn.shared ? true : false, scope: this.DataAccessObject, fnName: name, }); } }.bind(this)); // define DataAccessObject.prototype methods Object.keys(dao.prototype).forEach(function(name) { var fn = dao.prototype[name]; this.DataAccessObject.prototype[name] = fn; if (typeof fn === 'function') { this.defineOperation(name, { prototype: true, accepts: fn.accepts, 'returns': fn.returns, http: fn.http, remoteEnabled: fn.shared ? true : false, scope: this.DataAccessObject.prototype, fnName: name, }); } }.bind(this)); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function GeoPoint(data) { if (!(this instanceof GeoPoint)) { return new GeoPoint(data); } if (arguments.length === 2) { data = { lat: arguments[0], lng: arguments[1], }; } assert(Array.isArray(data) || typeof data === 'object' || typeof data === 'string', 'must provide valid geo-coordinates array [lat, lng] or object or a "lat, lng" string'); if (typeof data === 'string') { try { data = JSON.parse(data); } catch (err) { data = data.split(/,\s*/); assert(data.length === 2, 'must provide a string "lat,lng" creating a GeoPoint with a string'); } } if (Array.isArray(data)) { data = { lat: Number(data[0]), lng: Number(data[1]), }; } else { data.lng = Number(data.lng); data.lat = Number(data.lat); } assert(typeof data === 'object', 'must provide a lat and lng object when creating a GeoPoint'); assert(typeof data.lat === 'number' && !isNaN(data.lat), 'lat must be a number when creating a GeoPoint'); assert(typeof data.lng === 'number' && !isNaN(data.lng), 'lng must be a number when creating a GeoPoint'); assert(data.lng <= 180, 'lng must be <= 180'); assert(data.lng >= -180, 'lng must be >= -180'); assert(data.lat <= 90, 'lat must be <= 90'); assert(data.lat >= -90, 'lat must be >= -90'); this.lat = data.lat; this.lng = data.lng; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function MailConnector(settings) { assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object'); var transports = settings.transports; // if transports is not in settings object AND settings.transport exists if (!transports && settings.transport) { // then wrap single transport in an array and assign to transports transports = [settings.transport]; } if (!transports) { transports = []; } this.transportsIndex = {}; this.transports = []; if (loopback.isServer) { transports.forEach(this.setupTransport.bind(this)); } }
n/a
function Memory() { // TODO implement entire memory connector }
n/a
function RemoteConnector(settings) { assert(typeof settings === 'object', 'cannot initiaze RemoteConnector without a settings object'); this.client = settings.client; this.adapter = settings.adapter || 'rest'; this.protocol = settings.protocol || 'http'; this.root = settings.root || ''; this.host = settings.host || 'localhost'; this.port = settings.port || 3000; this.remotes = remoting.create(); this.name = 'remote-connector'; if (settings.url) { this.url = settings.url; } else { this.url = this.protocol + '://' + this.host + ':' + this.port + this.root; } // handle mixins in the define() method var DAO = this.DataAccessObject = function() { }; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function Route(path) { this.path = path; this.stack = []; debug('new %o', path) // route handlers for various http methods this.methods = {}; }
n/a
Router = function (options) { var opts = options || {}; function router(req, res, next) { router.handle(req, res, next); } // mixin Router class functions setPrototypeOf(router, proto) router.params = {}; router._params = []; router.caseSensitive = opts.caseSensitive; router.mergeParams = opts.mergeParams; router.strict = opts.strict; router.stack = []; return router; }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ModelConstructor(data, options) { if (!(this instanceof ModelConstructor)) { return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }
n/a
function ValidationError(obj) { if (!(this instanceof ValidationError)) return new ValidationError(obj); this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' ); this.statusCode = 422; this.details = { context: context, codes: obj.errors && obj.errors.codes, messages: obj.errors, }; if (Error.captureStackTrace) { // V8 (Chrome, Opera, Node) Error.captureStackTrace(this, this.constructor); } else if (errorHasStackProperty) { // Firefox this.stack = (new Error).stack; } // Safari and PhantomJS initializes `error.stack` on throw // Internet Explorer does not support `error.stack` }
...
return save();
}
inst.isValid(function(valid) {
if (valid) {
save();
} else {
var err = new Model.ValidationError(inst);
// throws option is dangerous for async usage
if (options.throws) {
throw err;
}
callback(err, inst);
}
});
...
configureModel = function (ModelCtor, config) { return this.registry.configureModel.apply(this.registry, arguments); }
...
}
config = extend({}, config);
config.dataSource = dataSource;
setSharedMethodSharedProperties(ModelCtor, app, config);
app.registry.configureModel(ModelCtor, config);
}
function setSharedMethodSharedProperties(model, app, modelConfigs) {
var settings = {};
// apply config.json settings
var config = app.get('remoting');
...
context = function () { throw new Error(g.f( '%s middleware was removed in version 3.0. See %s for more details.', 'loopback#context', 'http://loopback.io/doc/en/lb2/Using-current-context.html')); }
n/a
createContext = function (scopeName) { throw new Error(g.f( '%s was removed in version 3.0. See %s for more details.', 'loopback.createContext()', 'http://loopback.io/doc/en/lb2/Using-current-context.html')); }
...
'loopback.runInContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
};
loopback.createContext = function(scopeName) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.createContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
};
};
...
createDataSource = function (name, options) { return this.registry.createDataSource.apply(this.registry, arguments); }
...
config.connector = require(connectorPath);
}
}
if (config.connector && typeof config.connector === 'object' && !config.connector.name)
config.connector.name = name;
}
return registry.createDataSource(config);
}
function configureModel(ModelCtor, config, app) {
assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'),
ModelCtor.modelName + ' must be a descendant of loopback.Model');
var dataSource = config.dataSource;
...
createModel = function (name, properties, options) { return this.registry.createModel.apply(this.registry, arguments); }
...
app.model = function(Model, config) {
var isPublic = true;
var registry = this.registry;
if (typeof Model === 'string') {
var msg = 'app.model(modelName, settings) is no longer supported. ' +
'Use app.registry.createModel(modelName, definition) and ' +
'app.model(ModelCtor, config) instead.';
throw new Error(msg);
}
if (arguments.length > 1) {
config = config || {};
configureModel(Model, config, this);
...
errorHandler = function (options) { throw new Error('loopback.errorHandler is no longer available.' + ' Please use the module "strong-error-handler" instead.'); }
n/a
favicon = function (icon, options) { icon = icon || path.join(__dirname, '../../favicon.ico'); return favicon(icon, options); }
...
'use strict';
var favicon = require('serve-favicon');
var path = require('path');
/**
* Serve the LoopBack favicon.
* @header loopback.favicon()
*/
module.exports = function(icon, options) {
icon = icon || path.join(__dirname, '../../favicon.ico');
return favicon(icon, options);
};
...
findModel = function (modelName) { return this.registry.findModel.apply(this.registry, arguments); }
...
// the principalType must either be 'USER'
if (p.type === Principal.USER) {
return {id: p.id, principalType: p.type};
}
// or permit to resolve a valid user model
var userModel = this.registry.findModel(p.type);
if (!userModel) continue;
if (userModel.prototype instanceof BaseUser) {
return {id: p.id, principalType: p.type};
}
}
};
...
getCurrentContext = function () { throw new Error(g.f( '%s was removed in version 3.0. See %s for more details.', 'loopback.getCurrentContext()', 'http://loopback.io/doc/en/lb2/Using-current-context.html')); }
...
module.exports = function(loopback) {
juggler.getCurrentContext =
remoting.getCurrentContext =
loopback.getCurrentContext = function() {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.getCurrentContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
};
loopback.runInContext = function(fn) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.runInContext()',
...
getModel = function (modelName) { return this.registry.getModel.apply(this.registry, arguments); }
...
context = context || {};
assert(context.registry,
'Application registry is mandatory in AccessContext but missing in provided context');
this.registry = context.registry;
this.principals = context.principals || [];
var model = context.model;
model = ('string' === typeof model) ? this.registry.getModel(model) : model
;
this.model = model;
this.modelName = model && model.modelName;
this.modelId = context.id || context.modelId;
this.property = context.property || AccessContext.ALL;
this.method = context.method;
...
getModelByType = function (modelType) { return this.registry.getModelByType.apply(this.registry, arguments); }
...
// The function is used as a setter
_aclModel = ACL;
}
if (_aclModel) {
return _aclModel;
}
var aclModel = registry.getModel('ACL');
_aclModel = registry.getModelByType(aclModel);
return _aclModel;
};
/**
* Check if the given access token can invoke the specified method.
*
* @param {AccessToken} token The access token.
...
memory = function (name) { return this.registry.memory.apply(this.registry, arguments); }
n/a
function query(options) { var opts = Object.create(options || null); var queryparse = qs.parse; if (typeof options === 'function') { queryparse = options; opts = undefined; } if (opts !== undefined && opts.allowPrototypes === undefined) { // back-compat for qs module opts.allowPrototypes = true; } return function query(req, res, next){ if (!req.query) { var val = parseUrl(req).query; req.query = queryparse(val, opts); } next(); }; }
n/a
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'}; }
...
/**
* Enable remote invocation for the specified method.
* See [Remote methods](http://loopback.io/doc/en/lb2/Remote-methods.html) for more information.
*
* Static method example:
* ```js
* Model.myMethod();
* Model.remoteMethod('myMethod');
* ```
*
* @param {String} name The name of the method.
* @param {Object} options The remoting options.
* See [Remote methods - Options](http://loopback.io/doc/en/lb2/Remote-methods.html#options).
*/
...
function rest() { var handlers; // Cached handlers return function restApiHandler(req, res, next) { var app = req.app; var registry = app.registry; if (!handlers) { handlers = []; var remotingOptions = app.get('remoting') || {}; var contextOptions = remotingOptions.context; if (contextOptions !== undefined && contextOptions !== false) { throw new Error(g.f( '%s was removed in version 3.0. See %s for more details.', 'remoting.context option', 'http://loopback.io/doc/en/lb2/Using-current-context.html')); } if (app.isAuthEnabled) { var AccessToken = registry.getModelByType('AccessToken'); handlers.push(loopback.token({model: AccessToken, app: app})); } handlers.push(function(req, res, next) { // Need to get an instance of the REST handler per request return app.handler('rest')(req, res, next); }); } if (handlers.length === 1) { return handlers[0](req, res, next); } async.eachSeries(handlers, function(handler, done) { handler(req, res, done); }, next); }; }
...
module.exports = rest;
/**
* Expose models over REST.
*
* For example:
* ```js
* app.use(loopback.rest());
* ```
* For more information, see [Exposing models over a REST API](http://loopback.io/doc/en/lb2/Exposing-models-over-REST.html).
* @header loopback.rest()
*/
function rest() {
var handlers; // Cached handlers
...
runInContext = function (fn) { throw new Error(g.f( '%s was removed in version 3.0. See %s for more details.', 'loopback.runInContext()', 'http://loopback.io/doc/en/lb2/Using-current-context.html')); }
...
'loopback.getCurrentContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
};
loopback.runInContext = function(fn) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.runInContext()',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
};
loopback.createContext = function(scopeName) {
throw new Error(g.f(
'%s was removed in version 3.0. See %s for more details.',
'loopback.createContext()',
...
function serveStatic(root, options) { if (!root) { throw new TypeError('root path required') } if (typeof root !== 'string') { throw new TypeError('root path must be a string') } // copy options object var opts = Object.create(options || null) // fall-though var fallthrough = opts.fallthrough !== false // default redirect var redirect = opts.redirect !== false // headers listener var setHeaders = opts.setHeaders if (setHeaders && typeof setHeaders !== 'function') { throw new TypeError('option setHeaders must be function') } // setup options for send opts.maxage = opts.maxage || opts.maxAge || 0 opts.root = resolve(root) // construct directory listener var onDirectory = redirect ? createRedirectDirectoryListener() : createNotFoundDirectoryListener() return function serveStatic (req, res, next) { if (req.method !== 'GET' && req.method !== 'HEAD') { if (fallthrough) { return next() } // method not allowed res.statusCode = 405 res.setHeader('Allow', 'GET, HEAD') res.setHeader('Content-Length', '0') res.end() return } var forwardError = !fallthrough var originalUrl = parseUrl.original(req) var path = parseUrl(req).pathname // make sure redirect occurs at mount if (path === '/' && originalUrl.pathname.substr(-1) !== '/') { path = '' } // create send stream var stream = send(req, path, opts) // add directory handler stream.on('directory', onDirectory) // add headers listener if (setHeaders) { stream.on('headers', setHeaders) } // add file listener for fallthrough if (fallthrough) { stream.on('file', function onFile () { // once file is determined, always forward error forwardError = true }) } // forward errors stream.on('error', function error (err) { if (forwardError || !(err.statusCode < 500)) { next(err) return } next() }) // pipe stream.pipe(res) } }
...
* Serve static assets of a LoopBack application.
*
* @param {string} root The root directory from which the static assets are to
* be served.
* @param {object} options Refer to
* [express documentation](http://expressjs.com/4x/api.html#express.static)
* for the full list of available options.
* @header loopback.static(root, [options])
*/
'use strict';
module.exports = require('express').static;
...
function status() { var started = new Date(); return function(req, res) { res.send({ started: started, uptime: (Date.now() - Number(started)) / 1000, }); }; }
...
UserModel.afterRemote('confirm', function(ctx, inst, next) {
if (ctx.args.redirect !== undefined) {
if (!ctx.res) {
return next(new Error(g.f('The transport does not support HTTP redirects.')));
}
ctx.res.location(ctx.args.redirect);
ctx.res.status(302);
}
next();
});
// default models
assert(loopback.Email, 'Email model must be defined before User model');
UserModel.email = loopback.Email;
...
template = function (file) { var templates = this._templates || (this._templates = {}); var str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8')); return ejs.compile(str, { filename: file, }); }
...
});
}
}
return fn.promise;
};
function createVerificationEmailBody(options, cb) {
var template = loopback.template(options.template);
var body = template(options);
cb(null, body);
}
/**
* A default verification token generator which accepts the user the token is
* being generated for and a callback function to indicate completion.
...
function token(options) { options = options || {}; var TokenModel; var currentUserLiteral = options.currentUserLiteral; if (currentUserLiteral && (typeof currentUserLiteral !== 'string')) { debug('Set currentUserLiteral to \'me\' as the value is not a string.'); currentUserLiteral = 'me'; } if (typeof currentUserLiteral === 'string') { currentUserLiteral = escapeRegExp(currentUserLiteral); } var enableDoublecheck = !!options.enableDoublecheck; var overwriteExistingToken = !!options.overwriteExistingToken; return function(req, res, next) { var app = req.app; var registry = app.registry; if (!TokenModel) { TokenModel = registry.getModel(options.model || 'AccessToken'); } assert(typeof TokenModel === 'function', 'loopback.token() middleware requires a AccessToken model'); if (req.accessToken !== undefined) { if (!enableDoublecheck) { // req.accessToken is defined already (might also be "null" or "false") and enableDoublecheck // has not been set --> skip searching for credentials rewriteUserLiteral(req, currentUserLiteral); return next(); } if (req.accessToken && req.accessToken.id && !overwriteExistingToken) { // req.accessToken.id is defined, which means that some other middleware has identified a valid user. // when overwriteExistingToken is not set to a truthy value, skip searching for credentials. rewriteUserLiteral(req, currentUserLiteral); return next(); } // continue normal operation (as if req.accessToken was undefined) } TokenModel.findForRequest(req, options, function(err, token) { req.accessToken = token || null; rewriteUserLiteral(req, currentUserLiteral); var ctx = req.loopbackContext; if (ctx && ctx.active) ctx.set('accessToken', token); next(err); }); }; }
...
'%s was removed in version 3.0. See %s for more details.',
'remoting.context option',
'http://loopback.io/doc/en/lb2/Using-current-context.html'));
}
if (app.isAuthEnabled) {
var AccessToken = registry.getModelByType('AccessToken');
handlers.push(loopback.token({model: AccessToken, app: app}));
}
handlers.push(function(req, res, next) {
// Need to get an instance of the REST handler per request
return app.handler('rest')(req, res, next);
});
}
...
function urlNotFound() { return function raiseUrlNotFoundError(req, res, next) { var error = new Error('Cannot ' + req.method + ' ' + req.url); error.status = 404; next(error); }; }
...
*/
'use strict';
module.exports = urlNotFound;
/**
* Convert any request not handled so far to a 404 error
* to be handled by error-handling middleware.
* @header loopback.urlNotFound()
*/
function urlNotFound() {
return function raiseUrlNotFoundError(req, res, next) {
var error = new Error('Cannot ' + req.method + ' ' + req.url);
error.status = 404;
next(error);
};
...
function Registry() { this.defaultDataSources = {}; this.modelBuilder = new ModelBuilder(); require('./model')(this); require('./persisted-model')(this); // Set the default model base class. this.modelBuilder.defaultModelBaseClass = this.getModel('Model'); }
n/a
_defineRemoteMethods = function (ModelCtor, methods) { if (!methods) return; if (typeof methods !== 'object') { g.warn('Ignoring non-object "methods" setting of "%s".', ModelCtor.modelName); return; } Object.keys(methods).forEach(function(key) { var meta = methods[key]; var m = key.match(/^prototype\.(.*)$/); var isStatic = !m; if (typeof meta.isStatic !== 'boolean') { key = isStatic ? key : m[1]; meta.isStatic = isStatic; } else if (meta.isStatic && m) { throw new Error(g.f('Remoting metadata for %s.%s {{"isStatic"}} does ' + 'not match new method name-based style.', ModelCtor.modelName, key)); } else { key = isStatic ? key : m[1]; deprecated(g.f('Remoting metadata {{"isStatic"}} is deprecated. Please ' + 'specify {{"prototype.name"}} in method name instead for {{isStatic=false}}.')); } ModelCtor.remoteMethod(key, meta); }); }
...
}
}
BaseModel = BaseModel || this.getModel('PersistedModel');
var model = BaseModel.extend(name, properties, options);
model.registry = this;
this._defineRemoteMethods(model, model.settings.methods);
return model;
};
function buildModelOptionsFromConfig(config) {
var options = extend({}, config.options);
for (var key in config) {
...
configureModel = function (ModelCtor, config) { var settings = ModelCtor.settings; var modelName = ModelCtor.modelName; // Relations if (typeof config.relations === 'object' && config.relations !== null) { var relations = settings.relations = settings.relations || {}; Object.keys(config.relations).forEach(function(key) { // FIXME: [rfeng] We probably should check if the relation exists relations[key] = extend(relations[key] || {}, config.relations[key]); }); } else if (config.relations != null) { g.warn('The relations property of `%s` configuration ' + 'must be an object', modelName); } // ACLs if (Array.isArray(config.acls)) { var acls = settings.acls = settings.acls || []; config.acls.forEach(function(acl) { addACL(acls, acl); }); } else if (config.acls != null) { g.warn('The acls property of `%s` configuration ' + 'must be an array of objects', modelName); } // Settings var excludedProperties = { base: true, 'super': true, relations: true, acls: true, dataSource: true, }; if (typeof config.options === 'object' && config.options !== null) { for (var p in config.options) { if (!(p in excludedProperties)) { settings[p] = config.options[p]; } else { g.warn('Property `%s` cannot be reconfigured for `%s`', p, modelName); } } } else if (config.options != null) { g.warn('The options property of `%s` configuration ' + 'must be an object', modelName); } // 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); debug('Attached model `%s` to dataSource `%s`', modelName, config.dataSource.name); } else if (config.dataSource === null || config.dataSource === false) { debug('Model `%s` is not attached to any DataSource by configuration.', modelName); } else { debug('Model `%s` is not attached to any DataSource, possibly by a mistake.', modelName); g.warn( 'The configuration of `%s` is missing {{`dataSource`}} property.\n' + 'Use `null` or `false` to mark models not attached to any data source.', modelName); } var newMethodNames = config.methods && Object.keys(config.methods); var hasNewMethods = newMethodNames && newMethodNames.length; var hasDescendants = this.getModelByType(ModelCtor) !== ModelCtor; if (hasNewMethods && hasDescendants) { g.warn( 'Child models of `%s` will not inherit newly defined remote methods %s.', modelName, newMethodNames); } // Remote methods this._defineRemoteMethods(ModelCtor, config.methods); }
...
}
config = extend({}, config);
config.dataSource = dataSource;
setSharedMethodSharedProperties(ModelCtor, app, config);
app.registry.configureModel(ModelCtor, config);
}
function setSharedMethodSharedProperties(model, app, modelConfigs) {
var settings = {};
// apply config.json settings
var config = app.get('remoting');
...
createDataSource = function (name, options) { var self = this; var ds = new DataSource(name, options, self.modelBuilder); ds.createModel = function(name, properties, settings) { settings = settings || {}; var BaseModel = settings.base || settings.super; if (!BaseModel) { // Check the connector types var connectorTypes = ds.getTypes(); if (Array.isArray(connectorTypes) && connectorTypes.indexOf('db') !== -1) { // Only set up the base model to PersistedModel if the connector is DB BaseModel = self.PersistedModel; } else { BaseModel = self.Model; } settings.base = BaseModel; } var ModelCtor = self.createModel(name, properties, settings); ModelCtor.attachTo(ds); return ModelCtor; }; if (ds.settings && ds.settings.defaultForType) { var msg = g.f('{{DataSource}} option {{"defaultForType"}} is no longer supported'); throw new Error(msg); } return ds; }
...
config.connector = require(connectorPath);
}
}
if (config.connector && typeof config.connector === 'object' && !config.connector.name)
config.connector.name = name;
}
return registry.createDataSource(config);
}
function configureModel(ModelCtor, config, app) {
assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'),
ModelCtor.modelName + ' must be a descendant of loopback.Model');
var dataSource = config.dataSource;
...
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.findModel(BaseModel); if (!BaseModel) { throw new Error(g.f('Model not found: model `%s` is extending an unknown model `%s`.', name, baseName)); } } BaseModel = BaseModel || this.getModel('PersistedModel'); var model = BaseModel.extend(name, properties, options); model.registry = this; this._defineRemoteMethods(model, model.settings.methods); return model; }
...
app.model = function(Model, config) {
var isPublic = true;
var registry = this.registry;
if (typeof Model === 'string') {
var msg = 'app.model(modelName, settings) is no longer supported. ' +
'Use app.registry.createModel(modelName, definition) and ' +
'app.model(ModelCtor, config) instead.';
throw new Error(msg);
}
if (arguments.length > 1) {
config = config || {};
configureModel(Model, config, this);
...
findModel = function (modelName) { if (typeof modelName === 'function') return modelName; return this.modelBuilder.models[modelName]; }
...
// the principalType must either be 'USER'
if (p.type === Principal.USER) {
return {id: p.id, principalType: p.type};
}
// or permit to resolve a valid user model
var userModel = this.registry.findModel(p.type);
if (!userModel) continue;
if (userModel.prototype instanceof BaseUser) {
return {id: p.id, principalType: p.type};
}
}
};
...
getModel = function (modelName) { var model = this.findModel(modelName); if (model) return model; throw new Error(g.f('Model not found: %s', modelName)); }
...
context = context || {};
assert(context.registry,
'Application registry is mandatory in AccessContext but missing in provided context');
this.registry = context.registry;
this.principals = context.principals || [];
var model = context.model;
model = ('string' === typeof model) ? this.registry.getModel(model) : model
;
this.model = model;
this.modelName = model && model.modelName;
this.modelId = context.id || context.modelId;
this.property = context.property || AccessContext.ALL;
this.method = context.method;
...
getModelByType = function (modelType) { var type = typeof modelType; var accepted = ['function', 'string']; assert(accepted.indexOf(type) > -1, 'The model type must be a constructor or model name'); if (type === 'string') { modelType = this.getModel(modelType); } var models = this.modelBuilder.models; for (var m in models) { if (models[m].prototype instanceof modelType) { return models[m]; } } return modelType; }
...
// The function is used as a setter
_aclModel = ACL;
}
if (_aclModel) {
return _aclModel;
}
var aclModel = registry.getModel('ACL');
_aclModel = registry.getModelByType(aclModel);
return _aclModel;
};
/**
* Check if the given access token can invoke the specified method.
*
* @param {AccessToken} token The access token.
...
memory = function (name) { name = name || 'default'; var memory = ( this._memoryDataSources || (this._memoryDataSources = {}) )[name]; if (!memory) { memory = this._memoryDataSources[name] = this.createDataSource({ connector: 'memory', }); } return memory; }
n/a
accepts = function (){ var accept = accepts(this); return accept.types.apply(accept, arguments); }
n/a
acceptsCharset = function () { "use strict" log.call(deprecate, message, site) return fn.apply(this, arguments) }
n/a
acceptsCharsets = function (){ var accept = accepts(this); return accept.charsets.apply(accept, arguments); }
n/a
acceptsEncoding = function () { "use strict" log.call(deprecate, message, site) return fn.apply(this, arguments) }
n/a
acceptsEncodings = function (){ var accept = accepts(this); return accept.encodings.apply(accept, arguments); }
n/a
acceptsLanguage = function () { "use strict" log.call(deprecate, message, site) return fn.apply(this, arguments) }
n/a
acceptsLanguages = function (){ var accept = accepts(this); return accept.languages.apply(accept, arguments); }
n/a
function header(name) { if (!name) { throw new TypeError('name argument is required to req.get'); } if (typeof name !== 'string') { throw new TypeError('name must be a string to req.get'); } var lc = name.toLowerCase(); switch (lc) { case 'referer': case 'referrer': return this.headers.referrer || this.headers.referer; default: return this.headers[lc]; } }
...
* supports Express middleware. See
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
...
function header(name) { if (!name) { throw new TypeError('name argument is required to req.get'); } if (typeof name !== 'string') { throw new TypeError('name must be a string to req.get'); } var lc = name.toLowerCase(); switch (lc) { case 'referer': case 'referrer': return this.headers.referrer || this.headers.referer; default: return this.headers[lc]; } }
...
if (typeof id === 'string') {
return id;
}
}
for (i = 0, length = headers.length; i < length; i++) {
id = req.header(headers[i]);
if (typeof id === 'string') {
// Add support for oAuth 2.0 bearer token
// http://tools.ietf.org/html/rfc6750
if (id.indexOf('Bearer ') === 0) {
id = id.substring(7);
// Decode from base64
...
function is(types) { var arr = types; // support flattened arguments if (!Array.isArray(types)) { arr = new Array(arguments.length); for (var i = 0; i < arr.length; i++) { arr[i] = arguments[i]; } } return typeis(this, arr); }
n/a
function param(name, defaultValue) { var params = this.params || {}; var body = this.body || {}; var query = this.query || {}; var args = arguments.length === 1 ? 'name' : 'name, default'; deprecate('req.param(' + args + '): Use req.params, req.body, or req.query instead'); if (null != params[name] && params.hasOwnProperty(name)) return params[name]; if (null != body[name]) return body[name]; if (null != query[name]) return query[name]; return defaultValue; }
...
remotes.authorization = function(ctx, next) {
var method = ctx.method;
var req = ctx.req;
var Model = method.ctor;
var modelInstance = ctx.instance;
var modelId = modelInstance && modelInstance.id ||
// replacement for deprecated req.param()
(req.params && req.params.id !== undefined ? req.params.id :
req.body && req.body.id !== undefined ? req.body.id :
req.query && req.query.id !== undefined ? req.query.id :
undefined);
var modelName = Model.modelName;
...
function range(size, options) { var range = this.get('Range'); if (!range) return; return parseRange(size, range, options); }
n/a
function append(field, val) { var prev = this.get(field); var value = val; if (prev) { // concat the new and prev vals value = Array.isArray(prev) ? prev.concat(val) : Array.isArray(val) ? [prev].concat(val) : [prev, val]; } return this.set(field, value); }
n/a
function attachment(filename) { if (filename) { this.type(extname(filename)); } this.set('Content-Disposition', contentDisposition(filename)); return this; }
n/a
function clearCookie(name, options) { var opts = merge({ expires: new Date(1), path: '/' }, options); return this.cookie(name, '', opts); }
n/a
function contentType(type) { var ct = type.indexOf('/') === -1 ? mime.lookup(type) : type; return this.set('Content-Type', ct); }
n/a
cookie = function (name, value, options) { var opts = merge({}, options); var secret = this.req.secret; var signed = opts.signed; if (signed && !secret) { throw new Error('cookieParser("secret") required for signed cookies'); } var val = typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value); if (signed) { val = 's:' + sign(val, secret); } if ('maxAge' in opts) { opts.expires = new Date(Date.now() + opts.maxAge); opts.maxAge /= 1000; } if (opts.path == null) { opts.path = '/'; } this.append('Set-Cookie', cookie.serialize(name, String(val), opts)); return this; }
n/a
function download(path, filename, callback) { var done = callback; var name = filename; // support function as second arg if (typeof filename === 'function') { done = filename; name = null; } // set Content-Disposition when file is sent var headers = { 'Content-Disposition': contentDisposition(name || path) }; // Resolve the full path for sendFile var fullPath = resolve(path); return this.sendFile(fullPath, { headers: headers }, done); }
n/a
format = function (obj){ var req = this.req; var next = req.next; var fn = obj.default; if (fn) delete obj.default; var keys = Object.keys(obj); var key = keys.length > 0 ? req.accepts(keys) : false; this.vary("Accept"); if (key) { this.set('Content-Type', normalizeType(key).value); obj[key](req, this, next); } else if (fn) { fn(); } else { var err = new Error('Not Acceptable'); err.status = err.statusCode = 406; err.types = normalizeTypes(keys).map(function(o){ return o.value }); next(err); } return this; }
n/a
get = function (field){ return this.getHeader(field); }
...
* supports Express middleware. See
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
...
function header(field, val) { if (arguments.length === 2) { var value = Array.isArray(val) ? val.map(String) : String(val); // add charset to content-type if (field.toLowerCase() === 'content-type' && !charsetRegExp.test(value)) { var charset = mime.charsets.lookup(value.split(';')[0]); if (charset) value += '; charset=' + charset.toLowerCase(); } this.setHeader(field, value); } else { for (var key in field) { this.set(key, field[key]); } } return this; }
...
if (typeof id === 'string') {
return id;
}
}
for (i = 0, length = headers.length; i < length; i++) {
id = req.header(headers[i]);
if (typeof id === 'string') {
// Add support for oAuth 2.0 bearer token
// http://tools.ietf.org/html/rfc6750
if (id.indexOf('Bearer ') === 0) {
id = id.substring(7);
// Decode from base64
...
function json(obj) { var val = obj; // allow status / body if (arguments.length === 2) { // res.json(body, status) backwards compat if (typeof arguments[1] === 'number') { deprecate('res.json(obj, status): Use res.status(status).json(obj) instead'); this.statusCode = arguments[1]; } else { deprecate('res.json(status, obj): Use res.status(status).json(obj) instead'); this.statusCode = arguments[0]; val = arguments[1]; } } // settings var app = this.app; var replacer = app.get('json replacer'); var spaces = app.get('json spaces'); var body = stringify(val, replacer, spaces); // content-type if (!this.get('Content-Type')) { this.set('Content-Type', 'application/json'); } return this.send(body); }
n/a
function jsonp(obj) { var val = obj; // allow status / body if (arguments.length === 2) { // res.json(body, status) backwards compat if (typeof arguments[1] === 'number') { deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead'); this.statusCode = arguments[1]; } else { deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead'); this.statusCode = arguments[0]; val = arguments[1]; } } // settings var app = this.app; var replacer = app.get('json replacer'); var spaces = app.get('json spaces'); var body = stringify(val, replacer, spaces); var callback = this.req.query[app.get('jsonp callback name')]; // content-type if (!this.get('Content-Type')) { this.set('X-Content-Type-Options', 'nosniff'); this.set('Content-Type', 'application/json'); } // fixup callback if (Array.isArray(callback)) { callback = callback[0]; } // jsonp if (typeof callback === 'string' && callback.length !== 0) { this.charset = 'utf-8'; this.set('X-Content-Type-Options', 'nosniff'); this.set('Content-Type', 'text/javascript'); // restrict callback charset callback = callback.replace(/[^\[\]\w$.]/g, ''); // replace chars not allowed in JavaScript that are in JSON body = body .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse" // the typeof check is just to reduce client error noise body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');'; } return this.send(body); }
n/a
links = function (links){ var link = this.get('Link') || ''; if (link) link += ', '; return this.set('Link', link + Object.keys(links).map(function(rel){ return '<' + links[rel] + '>; rel="' + rel + '"'; }).join(', ')); }
n/a
function location(url) { var loc = url; // "back" is an alias for the referrer if (url === 'back') { loc = this.req.get('Referrer') || '/'; } // set location return this.set('Location', encodeUrl(loc)); }
...
);
UserModel.afterRemote('confirm', function(ctx, inst, next) {
if (ctx.args.redirect !== undefined) {
if (!ctx.res) {
return next(new Error(g.f('The transport does not support HTTP redirects.')));
}
ctx.res.location(ctx.args.redirect);
ctx.res.status(302);
}
next();
});
// default models
assert(loopback.Email, 'Email model must be defined before User model');
...
function redirect(url) { var address = url; var body; var status = 302; // allow status / url if (arguments.length === 2) { if (typeof arguments[0] === 'number') { status = arguments[0]; address = arguments[1]; } else { deprecate('res.redirect(url, status): Use res.redirect(status, url) instead'); status = arguments[1]; } } // Set location header address = this.location(address).get('Location'); // Support text/{plain,html} by default this.format({ text: function(){ body = statuses[status] + '. Redirecting to ' + address }, html: function(){ var u = escapeHtml(address); body = '<p>' + statuses[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>' }, default: function(){ body = ''; } }); // Respond this.statusCode = status; this.set('Content-Length', Buffer.byteLength(body)); if (this.req.method === 'HEAD') { this.end(); } else { this.end(body); } }
n/a
function render(view, options, callback) { var app = this.req.app; var done = callback; var opts = options || {}; var req = this.req; var self = this; // support callback function as second arg if (typeof options === 'function') { done = options; opts = {}; } // merge res.locals opts._locals = self.locals; // default callback to respond done = done || function (err, str) { if (err) return req.next(err); self.send(str); }; // render app.render(view, opts, done); }
n/a
function send(body) { var chunk = body; var encoding; var len; var req = this.req; var type; // settings var app = this.app; // allow status / body if (arguments.length === 2) { // res.send(body, status) backwards compat if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') { deprecate('res.send(body, status): Use res.status(status).send(body) instead'); this.statusCode = arguments[1]; } else { deprecate('res.send(status, body): Use res.status(status).send(body) instead'); this.statusCode = arguments[0]; chunk = arguments[1]; } } // disambiguate res.send(status) and res.send(status, num) if (typeof chunk === 'number' && arguments.length === 1) { // res.send(status) will set status message as text string if (!this.get('Content-Type')) { this.type('txt'); } deprecate('res.send(status): Use res.sendStatus(status) instead'); this.statusCode = chunk; chunk = statuses[chunk] } switch (typeof chunk) { // string defaulting to html case 'string': if (!this.get('Content-Type')) { this.type('html'); } break; case 'boolean': case 'number': case 'object': if (chunk === null) { chunk = ''; } else if (Buffer.isBuffer(chunk)) { if (!this.get('Content-Type')) { this.type('bin'); } } else { return this.json(chunk); } break; } // write strings in utf-8 if (typeof chunk === 'string') { encoding = 'utf8'; type = this.get('Content-Type'); // reflect this in content-type if (typeof type === 'string') { this.set('Content-Type', setCharset(type, 'utf-8')); } } // populate Content-Length if (chunk !== undefined) { if (!Buffer.isBuffer(chunk)) { // convert chunk to Buffer; saves later double conversions chunk = new Buffer(chunk, encoding); encoding = undefined; } len = chunk.length; this.set('Content-Length', len); } // populate ETag var etag; var generateETag = len !== undefined && app.get('etag fn'); if (typeof generateETag === 'function' && !this.get('ETag')) { if ((etag = generateETag(chunk, encoding))) { this.set('ETag', etag); } } // freshness if (req.fresh) this.statusCode = 304; // strip irrelevant headers if (204 === this.statusCode || 304 === this.statusCode) { this.removeHeader('Content-Type'); this.removeHeader('Content-Length'); this.removeHeader('Transfer-Encoding'); chunk = ''; } if (req.method === 'HEAD') { // skip body for HEAD this.end(); } else { // respond this.end(chunk, encoding); } return this; }
...
* [Express documentation](http://expressjs.com/) for details.
*
* ```js
* var loopback = require('loopback');
* var app = loopback();
*
* app.get('/', function(req, res){
* res.send('hello world');
* });
*
* app.listen(3000);
* ```
*
* @class LoopBackApplication
* @header var app = loopback()
...
function sendFile(path, options, callback) { var done = callback; var req = this.req; var res = this; var next = req.next; var opts = options || {}; if (!path) { throw new TypeError('path argument is required to res.sendFile'); } // support function as second arg if (typeof options === 'function') { done = options; opts = {}; } if (!opts.root && !isAbsolute(path)) { throw new TypeError('path must be absolute or specify root to res.sendFile'); } // create file stream var pathname = encodeURI(path); var file = send(req, pathname, opts); // transfer sendfile(res, file, opts, function (err) { if (done) return done(err); if (err && err.code === 'EISDIR') return next(); // next() all but write errors if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') { next(err); } }); }
n/a
function sendStatus(statusCode) { var body = statuses[statusCode] || String(statusCode) this.statusCode = statusCode; this.type('txt'); return this.send(body); }
n/a
sendfile = function (arg0, arg1, arg2) { "use strict" log.call(deprecate, message, site) return fn.apply(this, arguments) }
n/a
function header(field, val) { if (arguments.length === 2) { var value = Array.isArray(val) ? val.map(String) : String(val); // add charset to content-type if (field.toLowerCase() === 'content-type' && !charsetRegExp.test(value)) { var charset = mime.charsets.lookup(value.split(';')[0]); if (charset) value += '; charset=' + charset.toLowerCase(); } this.setHeader(field, value); } else { for (var key in field) { this.set(key, field[key]); } } return this; }
...
*
* For example, to listen on the specified port and all hosts, and ignore app config.
* ```js
* app.listen(80);
* ```
*
* The function also installs a `listening` callback that calls
* `app.set('port')` with the value returned by `server.address().port`.
* This way the port param contains always the real port number, even when
* listen was called with port number 0.
*
* @param {Function} [cb] If specified, the callback is added as a listener
* for the server's "listening" event.
* @returns {http.Server} A node `http.Server` with this application configured
* as the request handler.
...
function status(code) { this.statusCode = code; return this; }
...
UserModel.afterRemote('confirm', function(ctx, inst, next) {
if (ctx.args.redirect !== undefined) {
if (!ctx.res) {
return next(new Error(g.f('The transport does not support HTTP redirects.')));
}
ctx.res.location(ctx.args.redirect);
ctx.res.status(302);
}
next();
});
// default models
assert(loopback.Email, 'Email model must be defined before User model');
UserModel.email = loopback.Email;
...
function contentType(type) { var ct = type.indexOf('/') === -1 ? mime.lookup(type) : type; return this.set('Content-Type', ct); }
...
filter.where[idName] = {inq: ids};
model.find(filter, function(err, models) {
if (err) return callback(err);
var modelIds = models.map(function(m) {
return m[idName].toString();
});
callback(null, changes.filter(function(ch) {
if (ch.type() === Change.DELETE) return true;
return modelIds.indexOf(ch.modelId) > -1;
}));
});
});
};
/**
...
vary = function (field){ // checks for back-compat if (!field || (Array.isArray(field) && !field.length)) { deprecate('res.vary(): Provide a field name'); return this; } vary(this, field); return this; }
n/a
function concatResults(previousResults, currentResults) { if (Array.isArray(currentResults)) { previousResults = previousResults.concat(currentResults); } else if (typeof currentResults === 'object') { Object.keys(currentResults).forEach(function(key) { previousResults[key] = concatResults(previousResults[key], currentResults[key]); }); } else { previousResults = currentResults; } return previousResults; }
n/a
function createPromiseCallback() { var cb; var promise = new Promise(function(resolve, reject) { cb = function(err, data) { if (err) return reject(err); return resolve(data); }; }); cb.promise = promise; return cb; }
...
if (typeof options === 'function') {
options = {};
}
options = options || {};
var sourceModel = this;
callback = callback || utils.createPromiseCallback();
debug('replicating %s since %s to %s since %s',
sourceModel.modelName,
since.source,
targetModel.modelName,
since.target);
if (options.filter) {
...
function downloadInChunks(filter, chunkSize, processFunction, cb) { var results = []; filter = filter ? JSON.parse(JSON.stringify(filter)) : {}; if (!chunkSize || chunkSize < 1) { // if chunking not required processFunction(filter, cb); } else { filter.skip = 0; filter.limit = chunkSize; processFunction(JSON.parse(JSON.stringify(filter)), pageAndConcatResults); } function pageAndConcatResults(err, pagedResults) { if (err) { return cb(err); } else { results = concatResults(results, pagedResults); if (pagedResults.length >= chunkSize) { filter.skip += pagedResults.length; processFunction(JSON.parse(JSON.stringify(filter)), pageAndConcatResults); } else { cb(null, results); } } } }
...
createSourceUpdates,
bulkUpdate,
];
async.waterfall(tasks, done);
function getSourceChanges(cb) {
utils.downloadInChunks(
options.filter,
replicationChunkSize,
function(filter, pagingCallback) {
sourceModel.changes(since.source, filter, pagingCallback);
},
debug.enabled ? log : cb);
...
function uploadInChunks(largeArray, chunkSize, processFunction, cb) { var chunkArrays = []; if (!chunkSize || chunkSize < 1 || largeArray.length <= chunkSize) { // if chunking not required processFunction(largeArray, cb); } else { // copying so that the largeArray object does not get affected during splice var copyOfLargeArray = [].concat(largeArray); // chunking to smaller arrays while (copyOfLargeArray.length > 0) { chunkArrays.push(copyOfLargeArray.splice(0, chunkSize)); } var tasks = chunkArrays.map(function(chunkArray) { return function(previousResults, chunkCallback) { var lastArg = arguments[arguments.length - 1]; if (typeof lastArg === 'function') { chunkCallback = lastArg; } processFunction(chunkArray, function(err, results) { if (err) { return chunkCallback(err); } // if this is the first async waterfall call or if previous results was not defined if (typeof previousResults === 'function' || typeof previousResults === 'undefined' || previousResults === null) { previousResults = results; } else if (results) { previousResults = concatResults(previousResults, results); } chunkCallback(err, previousResults); }); }; }); async.waterfall(tasks, cb); } }
...
debug('\tusing source changes');
result.forEach(function(it) { debug('\t\t%j', it); });
cb(err, result);
}
}
function getDiffFromTarget(sourceChanges, cb) {
utils.uploadInChunks(
sourceChanges,
replicationChunkSize,
function(smallArray, chunkCallback) {
return targetModel.diff(since.target, smallArray, chunkCallback);
},
debug.enabled ? log : cb);
...