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);
...