models: move Change LDL def into a json file
This commit is contained in:
parent
6cbc231fba
commit
0906a6f5b3
|
@ -10,26 +10,6 @@ var PersistedModel = require('../../lib/loopback').PersistedModel
|
||||||
, assert = require('assert')
|
, assert = require('assert')
|
||||||
, debug = require('debug')('loopback:change');
|
, debug = require('debug')('loopback:change');
|
||||||
|
|
||||||
/*!
|
|
||||||
* Properties
|
|
||||||
*/
|
|
||||||
|
|
||||||
var properties = {
|
|
||||||
id: {type: String, id: true},
|
|
||||||
rev: {type: String},
|
|
||||||
prev: {type: String},
|
|
||||||
checkpoint: {type: Number},
|
|
||||||
modelName: {type: String},
|
|
||||||
modelId: {type: String}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Options
|
|
||||||
*/
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
trackChanges: false
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change list entry.
|
* Change list entry.
|
||||||
|
@ -41,45 +21,45 @@ var options = {
|
||||||
* @property {String} modelName Model name
|
* @property {String} modelName Model name
|
||||||
* @property {String} modelId Model ID
|
* @property {String} modelId Model ID
|
||||||
*
|
*
|
||||||
* @class
|
* @class Change
|
||||||
* @inherits {Model}
|
* @inherits {PersistedModel}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Change = module.exports = PersistedModel.extend('Change', properties, options);
|
module.exports = function(Change) {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Constants
|
* Constants
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.UPDATE = 'update';
|
Change.UPDATE = 'update';
|
||||||
Change.CREATE = 'create';
|
Change.CREATE = 'create';
|
||||||
Change.DELETE = 'delete';
|
Change.DELETE = 'delete';
|
||||||
Change.UNKNOWN = 'unknown';
|
Change.UNKNOWN = 'unknown';
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Conflict Class
|
* Conflict Class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.Conflict = Conflict;
|
Change.Conflict = Conflict;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Setup the extended model.
|
* Setup the extended model.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.setup = function() {
|
Change.setup = function() {
|
||||||
PersistedModel.setup.call(this);
|
PersistedModel.setup.call(this);
|
||||||
var Change = this;
|
var Change = this;
|
||||||
|
|
||||||
Change.getter.id = function() {
|
Change.getter.id = function() {
|
||||||
var hasModel = this.modelName && this.modelId;
|
var hasModel = this.modelName && this.modelId;
|
||||||
if(!hasModel) return null;
|
if (!hasModel) return null;
|
||||||
|
|
||||||
return Change.idForModel(this.modelName, this.modelId);
|
return Change.idForModel(this.modelName, this.modelId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Change.setup();
|
Change.setup();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track the recent change of the given modelIds.
|
* Track the recent change of the given modelIds.
|
||||||
*
|
*
|
||||||
* @param {String} modelName
|
* @param {String} modelName
|
||||||
|
@ -89,22 +69,22 @@ Change.setup();
|
||||||
* @param {Array} changes Changes that were tracked
|
* @param {Array} changes Changes that were tracked
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.rectifyModelChanges = function(modelName, modelIds, callback) {
|
Change.rectifyModelChanges = function(modelName, modelIds, callback) {
|
||||||
var tasks = [];
|
var tasks = [];
|
||||||
var Change = this;
|
var Change = this;
|
||||||
|
|
||||||
modelIds.forEach(function(id) {
|
modelIds.forEach(function(id) {
|
||||||
tasks.push(function(cb) {
|
tasks.push(function(cb) {
|
||||||
Change.findOrCreateChange(modelName, id, function(err, change) {
|
Change.findOrCreateChange(modelName, id, function(err, change) {
|
||||||
if(err) return Change.handleError(err, cb);
|
if (err) return Change.handleError(err, cb);
|
||||||
change.rectify(cb);
|
change.rectify(cb);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
async.parallel(tasks, callback);
|
async.parallel(tasks, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an identifier for a given model.
|
* Get an identifier for a given model.
|
||||||
*
|
*
|
||||||
* @param {String} modelName
|
* @param {String} modelName
|
||||||
|
@ -112,11 +92,11 @@ Change.rectifyModelChanges = function(modelName, modelIds, callback) {
|
||||||
* @return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.idForModel = function(modelName, modelId) {
|
Change.idForModel = function(modelName, modelId) {
|
||||||
return this.hash([modelName, modelId].join('-'));
|
return this.hash([modelName, modelId].join('-'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find or create a change for the given model.
|
* Find or create a change for the given model.
|
||||||
*
|
*
|
||||||
* @param {String} modelName
|
* @param {String} modelName
|
||||||
|
@ -127,14 +107,14 @@ Change.idForModel = function(modelName, modelId) {
|
||||||
* @end
|
* @end
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.findOrCreateChange = function(modelName, modelId, callback) {
|
Change.findOrCreateChange = function(modelName, modelId, callback) {
|
||||||
assert(loopback.findModel(modelName), modelName + ' does not exist');
|
assert(loopback.findModel(modelName), modelName + ' does not exist');
|
||||||
var id = this.idForModel(modelName, modelId);
|
var id = this.idForModel(modelName, modelId);
|
||||||
var Change = this;
|
var Change = this;
|
||||||
|
|
||||||
this.findById(id, function(err, change) {
|
this.findById(id, function(err, change) {
|
||||||
if(err) return callback(err);
|
if (err) return callback(err);
|
||||||
if(change) {
|
if (change) {
|
||||||
callback(null, change);
|
callback(null, change);
|
||||||
} else {
|
} else {
|
||||||
var ch = new Change({
|
var ch = new Change({
|
||||||
|
@ -146,9 +126,9 @@ Change.findOrCreateChange = function(modelName, modelId, callback) {
|
||||||
ch.save(callback);
|
ch.save(callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update (or create) the change with the current revision.
|
* Update (or create) the change with the current revision.
|
||||||
*
|
*
|
||||||
* @callback {Function} callback
|
* @callback {Function} callback
|
||||||
|
@ -156,7 +136,7 @@ Change.findOrCreateChange = function(modelName, modelId, callback) {
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.prototype.rectify = function(cb) {
|
Change.prototype.rectify = function(cb) {
|
||||||
var change = this;
|
var change = this;
|
||||||
var tasks = [
|
var tasks = [
|
||||||
updateRevision,
|
updateRevision,
|
||||||
|
@ -167,12 +147,12 @@ Change.prototype.rectify = function(cb) {
|
||||||
change.debug('rectify change');
|
change.debug('rectify change');
|
||||||
|
|
||||||
cb = cb || function(err) {
|
cb = cb || function(err) {
|
||||||
if(err) throw new Error(err);
|
if (err) throw new Error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
async.parallel(tasks, function(err) {
|
async.parallel(tasks, function(err) {
|
||||||
if(err) return cb(err);
|
if (err) return cb(err);
|
||||||
if(change.prev === Change.UNKNOWN) {
|
if (change.prev === Change.UNKNOWN) {
|
||||||
// this occurs when a record of a change doesn't exist
|
// this occurs when a record of a change doesn't exist
|
||||||
// and its current revision is null (not found)
|
// and its current revision is null (not found)
|
||||||
change.remove(cb);
|
change.remove(cb);
|
||||||
|
@ -184,10 +164,10 @@ Change.prototype.rectify = function(cb) {
|
||||||
function updateRevision(cb) {
|
function updateRevision(cb) {
|
||||||
// get the current revision
|
// get the current revision
|
||||||
change.currentRevision(function(err, rev) {
|
change.currentRevision(function(err, rev) {
|
||||||
if(err) return Change.handleError(err, cb);
|
if (err) return Change.handleError(err, cb);
|
||||||
if(rev) {
|
if (rev) {
|
||||||
// avoid setting rev and prev to the same value
|
// avoid setting rev and prev to the same value
|
||||||
if(currentRev !== rev) {
|
if (currentRev !== rev) {
|
||||||
change.rev = rev;
|
change.rev = rev;
|
||||||
change.prev = currentRev;
|
change.prev = currentRev;
|
||||||
} else {
|
} else {
|
||||||
|
@ -195,9 +175,9 @@ Change.prototype.rectify = function(cb) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
change.rev = null;
|
change.rev = null;
|
||||||
if(currentRev) {
|
if (currentRev) {
|
||||||
change.prev = currentRev;
|
change.prev = currentRev;
|
||||||
} else if(!change.prev) {
|
} else if (!change.prev) {
|
||||||
change.debug('ERROR - could not determing prev');
|
change.debug('ERROR - could not determing prev');
|
||||||
change.prev = Change.UNKNOWN;
|
change.prev = Change.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
@ -209,34 +189,34 @@ Change.prototype.rectify = function(cb) {
|
||||||
|
|
||||||
function updateCheckpoint(cb) {
|
function updateCheckpoint(cb) {
|
||||||
change.constructor.getCheckpointModel().current(function(err, checkpoint) {
|
change.constructor.getCheckpointModel().current(function(err, checkpoint) {
|
||||||
if(err) return Change.handleError(err);
|
if (err) return Change.handleError(err);
|
||||||
change.checkpoint = checkpoint;
|
change.checkpoint = checkpoint;
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a change's current revision based on current data.
|
* Get a change's current revision based on current data.
|
||||||
* @callback {Function} callback
|
* @callback {Function} callback
|
||||||
* @param {Error} err
|
* @param {Error} err
|
||||||
* @param {String} rev The current revision
|
* @param {String} rev The current revision
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.prototype.currentRevision = function(cb) {
|
Change.prototype.currentRevision = function(cb) {
|
||||||
var model = this.getModelCtor();
|
var model = this.getModelCtor();
|
||||||
var id = this.getModelId();
|
var id = this.getModelId();
|
||||||
model.findById(id, function(err, inst) {
|
model.findById(id, function(err, inst) {
|
||||||
if(err) return Change.handleError(err, cb);
|
if (err) return Change.handleError(err, cb);
|
||||||
if(inst) {
|
if (inst) {
|
||||||
cb(null, Change.revisionForInst(inst));
|
cb(null, Change.revisionForInst(inst));
|
||||||
} else {
|
} else {
|
||||||
cb(null, null);
|
cb(null, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a hash of the given `string` with the `options.hashAlgorithm`.
|
* Create a hash of the given `string` with the `options.hashAlgorithm`.
|
||||||
* **Default: `sha1`**
|
* **Default: `sha1`**
|
||||||
*
|
*
|
||||||
|
@ -244,24 +224,24 @@ Change.prototype.currentRevision = function(cb) {
|
||||||
* @return {String} The hashed string
|
* @return {String} The hashed string
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.hash = function(str) {
|
Change.hash = function(str) {
|
||||||
return crypto
|
return crypto
|
||||||
.createHash(Change.settings.hashAlgorithm || 'sha1')
|
.createHash(Change.settings.hashAlgorithm || 'sha1')
|
||||||
.update(str)
|
.update(str)
|
||||||
.digest('hex');
|
.digest('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the revision string for the given object
|
* Get the revision string for the given object
|
||||||
* @param {Object} inst The data to get the revision string for
|
* @param {Object} inst The data to get the revision string for
|
||||||
* @return {String} The revision string
|
* @return {String} The revision string
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.revisionForInst = function(inst) {
|
Change.revisionForInst = function(inst) {
|
||||||
return this.hash(CJSON.stringify(inst));
|
return this.hash(CJSON.stringify(inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a change's type. Returns one of:
|
* Get a change's type. Returns one of:
|
||||||
*
|
*
|
||||||
* - `Change.UPDATE`
|
* - `Change.UPDATE`
|
||||||
|
@ -272,69 +252,69 @@ Change.revisionForInst = function(inst) {
|
||||||
* @return {String} the type of change
|
* @return {String} the type of change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.prototype.type = function() {
|
Change.prototype.type = function() {
|
||||||
if(this.rev && this.prev) {
|
if (this.rev && this.prev) {
|
||||||
return Change.UPDATE;
|
return Change.UPDATE;
|
||||||
}
|
}
|
||||||
if(this.rev && !this.prev) {
|
if (this.rev && !this.prev) {
|
||||||
return Change.CREATE;
|
return Change.CREATE;
|
||||||
}
|
}
|
||||||
if(!this.rev && this.prev) {
|
if (!this.rev && this.prev) {
|
||||||
return Change.DELETE;
|
return Change.DELETE;
|
||||||
}
|
}
|
||||||
return Change.UNKNOWN;
|
return Change.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare two changes.
|
* Compare two changes.
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.prototype.equals = function(change) {
|
Change.prototype.equals = function(change) {
|
||||||
if(!change) return false;
|
if (!change) return false;
|
||||||
var thisRev = this.rev || null;
|
var thisRev = this.rev || null;
|
||||||
var thatRev = change.rev || null;
|
var thatRev = change.rev || null;
|
||||||
return thisRev === thatRev;
|
return thisRev === thatRev;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does this change conflict with the given change.
|
* Does this change conflict with the given change.
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.prototype.conflictsWith = function(change) {
|
Change.prototype.conflictsWith = function(change) {
|
||||||
if(!change) return false;
|
if (!change) return false;
|
||||||
if(this.equals(change)) return false;
|
if (this.equals(change)) return false;
|
||||||
if(Change.bothDeleted(this, change)) return false;
|
if (Change.bothDeleted(this, change)) return false;
|
||||||
if(this.isBasedOn(change)) return false;
|
if (this.isBasedOn(change)) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Are both changes deletes?
|
* Are both changes deletes?
|
||||||
* @param {Change} a
|
* @param {Change} a
|
||||||
* @param {Change} b
|
* @param {Change} b
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.bothDeleted = function(a, b) {
|
Change.bothDeleted = function(a, b) {
|
||||||
return a.type() === Change.DELETE
|
return a.type() === Change.DELETE
|
||||||
&& b.type() === Change.DELETE;
|
&& b.type() === Change.DELETE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the change is based on the given change.
|
* Determine if the change is based on the given change.
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.prototype.isBasedOn = function(change) {
|
Change.prototype.isBasedOn = function(change) {
|
||||||
return this.prev === change.rev;
|
return this.prev === change.rev;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the differences for a given model since a given checkpoint.
|
* Determine the differences for a given model since a given checkpoint.
|
||||||
*
|
*
|
||||||
* The callback will contain an error or `result`.
|
* The callback will contain an error or `result`.
|
||||||
|
@ -364,7 +344,7 @@ Change.prototype.isBasedOn = function(change) {
|
||||||
* @param {Object} result See above.
|
* @param {Object} result See above.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.diff = function(modelName, since, remoteChanges, callback) {
|
Change.diff = function(modelName, since, remoteChanges, callback) {
|
||||||
var remoteChangeIndex = {};
|
var remoteChangeIndex = {};
|
||||||
var modelIds = [];
|
var modelIds = [];
|
||||||
remoteChanges.forEach(function(ch) {
|
remoteChanges.forEach(function(ch) {
|
||||||
|
@ -381,7 +361,7 @@ Change.diff = function(modelName, since, remoteChanges, callback) {
|
||||||
checkpoint: {gte: since}
|
checkpoint: {gte: since}
|
||||||
}
|
}
|
||||||
}, function(err, localChanges) {
|
}, function(err, localChanges) {
|
||||||
if(err) return callback(err);
|
if (err) return callback(err);
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
var conflicts = [];
|
var conflicts = [];
|
||||||
var localModelIds = [];
|
var localModelIds = [];
|
||||||
|
@ -390,8 +370,8 @@ Change.diff = function(modelName, since, remoteChanges, callback) {
|
||||||
localChange = new Change(localChange);
|
localChange = new Change(localChange);
|
||||||
localModelIds.push(localChange.modelId);
|
localModelIds.push(localChange.modelId);
|
||||||
var remoteChange = remoteChangeIndex[localChange.modelId];
|
var remoteChange = remoteChangeIndex[localChange.modelId];
|
||||||
if(remoteChange && !localChange.equals(remoteChange)) {
|
if (remoteChange && !localChange.equals(remoteChange)) {
|
||||||
if(remoteChange.conflictsWith(localChange)) {
|
if (remoteChange.conflictsWith(localChange)) {
|
||||||
remoteChange.debug('remote conflict');
|
remoteChange.debug('remote conflict');
|
||||||
localChange.debug('local conflict');
|
localChange.debug('local conflict');
|
||||||
conflicts.push(localChange);
|
conflicts.push(localChange);
|
||||||
|
@ -403,7 +383,7 @@ Change.diff = function(modelName, since, remoteChanges, callback) {
|
||||||
});
|
});
|
||||||
|
|
||||||
modelIds.forEach(function(id) {
|
modelIds.forEach(function(id) {
|
||||||
if(localModelIds.indexOf(id) === -1) {
|
if (localModelIds.indexOf(id) === -1) {
|
||||||
deltas.push(remoteChangeIndex[id]);
|
deltas.push(remoteChangeIndex[id]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -413,49 +393,49 @@ Change.diff = function(modelName, since, remoteChanges, callback) {
|
||||||
conflicts: conflicts
|
conflicts: conflicts
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Correct all change list entries.
|
* Correct all change list entries.
|
||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.rectifyAll = function(cb) {
|
Change.rectifyAll = function(cb) {
|
||||||
debug('rectify all');
|
debug('rectify all');
|
||||||
var Change = this;
|
var Change = this;
|
||||||
// this should be optimized
|
// this should be optimized
|
||||||
this.find(function(err, changes) {
|
this.find(function(err, changes) {
|
||||||
if(err) return cb(err);
|
if (err) return cb(err);
|
||||||
changes.forEach(function(change) {
|
changes.forEach(function(change) {
|
||||||
change = new Change(change);
|
change = new Change(change);
|
||||||
change.rectify();
|
change.rectify();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the checkpoint model.
|
* Get the checkpoint model.
|
||||||
* @return {Checkpoint}
|
* @return {Checkpoint}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.getCheckpointModel = function() {
|
Change.getCheckpointModel = function() {
|
||||||
var checkpointModel = this.Checkpoint;
|
var checkpointModel = this.Checkpoint;
|
||||||
if(checkpointModel) return checkpointModel;
|
if (checkpointModel) return checkpointModel;
|
||||||
this.checkpoint = checkpointModel = loopback.Checkpoint.extend('checkpoint');
|
this.checkpoint = checkpointModel = loopback.Checkpoint.extend('checkpoint');
|
||||||
assert(this.dataSource, 'Cannot getCheckpointModel(): ' + this.modelName
|
assert(this.dataSource, 'Cannot getCheckpointModel(): ' + this.modelName
|
||||||
+ ' is not attached to a dataSource');
|
+ ' is not attached to a dataSource');
|
||||||
checkpointModel.attachTo(this.dataSource);
|
checkpointModel.attachTo(this.dataSource);
|
||||||
return checkpointModel;
|
return checkpointModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
Change.handleError = function(err) {
|
Change.handleError = function(err) {
|
||||||
if(!this.settings.ignoreErrors) {
|
if (!this.settings.ignoreErrors) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Change.prototype.debug = function() {
|
Change.prototype.debug = function() {
|
||||||
if(debug.enabled) {
|
if (debug.enabled) {
|
||||||
var args = Array.prototype.slice.call(arguments);
|
var args = Array.prototype.slice.call(arguments);
|
||||||
debug.apply(this, args);
|
debug.apply(this, args);
|
||||||
debug('\tid', this.id);
|
debug('\tid', this.id);
|
||||||
|
@ -465,33 +445,33 @@ Change.prototype.debug = function() {
|
||||||
debug('\tmodelId', this.modelId);
|
debug('\tmodelId', this.modelId);
|
||||||
debug('\ttype', this.type());
|
debug('\ttype', this.type());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the `Model` class for `change.modelName`.
|
* Get the `Model` class for `change.modelName`.
|
||||||
* @return {Model}
|
* @return {Model}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Change.prototype.getModelCtor = function() {
|
Change.prototype.getModelCtor = function() {
|
||||||
return this.constructor.settings.trackModel;
|
return this.constructor.settings.trackModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
Change.prototype.getModelId = function() {
|
Change.prototype.getModelId = function() {
|
||||||
// TODO(ritch) get rid of the need to create an instance
|
// TODO(ritch) get rid of the need to create an instance
|
||||||
var Model = this.getModelCtor();
|
var Model = this.getModelCtor();
|
||||||
var id = this.modelId;
|
var id = this.modelId;
|
||||||
var m = new Model();
|
var m = new Model();
|
||||||
m.setId(id);
|
m.setId(id);
|
||||||
return m.getId();
|
return m.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
Change.prototype.getModel = function(callback) {
|
Change.prototype.getModel = function(callback) {
|
||||||
var Model = this.constructor.settings.trackModel;
|
var Model = this.constructor.settings.trackModel;
|
||||||
var id = this.getModelId();
|
var id = this.getModelId();
|
||||||
Model.findById(id, callback);
|
Model.findById(id, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When two changes conflict a conflict is created.
|
* When two changes conflict a conflict is created.
|
||||||
*
|
*
|
||||||
* **Note: call `conflict.fetch()` to get the `target` and `source` models.
|
* **Note: call `conflict.fetch()` to get the `target` and `source` models.
|
||||||
|
@ -501,17 +481,18 @@ Change.prototype.getModel = function(callback) {
|
||||||
* @param {PersistedModel} TargetModel
|
* @param {PersistedModel} TargetModel
|
||||||
* @property {ModelClass} source The source model instance
|
* @property {ModelClass} source The source model instance
|
||||||
* @property {ModelClass} target The target model instance
|
* @property {ModelClass} target The target model instance
|
||||||
|
* @class Change.Conflict
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Conflict(modelId, SourceModel, TargetModel) {
|
function Conflict(modelId, SourceModel, TargetModel) {
|
||||||
this.SourceModel = SourceModel;
|
this.SourceModel = SourceModel;
|
||||||
this.TargetModel = TargetModel;
|
this.TargetModel = TargetModel;
|
||||||
this.SourceChange = SourceModel.getChangeModel();
|
this.SourceChange = SourceModel.getChangeModel();
|
||||||
this.TargetChange = TargetModel.getChangeModel();
|
this.TargetChange = TargetModel.getChangeModel();
|
||||||
this.modelId = modelId;
|
this.modelId = modelId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the conflicting models.
|
* Fetch the conflicting models.
|
||||||
*
|
*
|
||||||
* @callback {Function} callback
|
* @callback {Function} callback
|
||||||
|
@ -520,7 +501,7 @@ function Conflict(modelId, SourceModel, TargetModel) {
|
||||||
* @param {PersistedModel} target
|
* @param {PersistedModel} target
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Conflict.prototype.models = function(cb) {
|
Conflict.prototype.models = function(cb) {
|
||||||
var conflict = this;
|
var conflict = this;
|
||||||
var SourceModel = this.SourceModel;
|
var SourceModel = this.SourceModel;
|
||||||
var TargetModel = this.TargetModel;
|
var TargetModel = this.TargetModel;
|
||||||
|
@ -534,7 +515,7 @@ Conflict.prototype.models = function(cb) {
|
||||||
|
|
||||||
function getSourceModel(cb) {
|
function getSourceModel(cb) {
|
||||||
SourceModel.findById(conflict.modelId, function(err, model) {
|
SourceModel.findById(conflict.modelId, function(err, model) {
|
||||||
if(err) return cb(err);
|
if (err) return cb(err);
|
||||||
source = model;
|
source = model;
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
|
@ -542,19 +523,19 @@ Conflict.prototype.models = function(cb) {
|
||||||
|
|
||||||
function getTargetModel(cb) {
|
function getTargetModel(cb) {
|
||||||
TargetModel.findById(conflict.modelId, function(err, model) {
|
TargetModel.findById(conflict.modelId, function(err, model) {
|
||||||
if(err) return cb(err);
|
if (err) return cb(err);
|
||||||
target = model;
|
target = model;
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function done(err) {
|
function done(err) {
|
||||||
if(err) return cb(err);
|
if (err) return cb(err);
|
||||||
cb(null, source, target);
|
cb(null, source, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the conflicting changes.
|
* Get the conflicting changes.
|
||||||
*
|
*
|
||||||
* @callback {Function} callback
|
* @callback {Function} callback
|
||||||
|
@ -563,7 +544,7 @@ Conflict.prototype.models = function(cb) {
|
||||||
* @param {Change} targetChange
|
* @param {Change} targetChange
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Conflict.prototype.changes = function(cb) {
|
Conflict.prototype.changes = function(cb) {
|
||||||
var conflict = this;
|
var conflict = this;
|
||||||
var sourceChange;
|
var sourceChange;
|
||||||
var targetChange;
|
var targetChange;
|
||||||
|
@ -577,7 +558,7 @@ Conflict.prototype.changes = function(cb) {
|
||||||
conflict.SourceChange.findOne({where: {
|
conflict.SourceChange.findOne({where: {
|
||||||
modelId: conflict.modelId
|
modelId: conflict.modelId
|
||||||
}}, function(err, change) {
|
}}, function(err, change) {
|
||||||
if(err) return cb(err);
|
if (err) return cb(err);
|
||||||
sourceChange = change;
|
sourceChange = change;
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
|
@ -587,35 +568,35 @@ Conflict.prototype.changes = function(cb) {
|
||||||
conflict.TargetChange.findOne({where: {
|
conflict.TargetChange.findOne({where: {
|
||||||
modelId: conflict.modelId
|
modelId: conflict.modelId
|
||||||
}}, function(err, change) {
|
}}, function(err, change) {
|
||||||
if(err) return cb(err);
|
if (err) return cb(err);
|
||||||
targetChange = change;
|
targetChange = change;
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function done(err) {
|
function done(err) {
|
||||||
if(err) return cb(err);
|
if (err) return cb(err);
|
||||||
cb(null, sourceChange, targetChange);
|
cb(null, sourceChange, targetChange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the conflict.
|
* Resolve the conflict.
|
||||||
*
|
*
|
||||||
* @callback {Function} callback
|
* @callback {Function} callback
|
||||||
* @param {Error} err
|
* @param {Error} err
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Conflict.prototype.resolve = function(cb) {
|
Conflict.prototype.resolve = function(cb) {
|
||||||
var conflict = this;
|
var conflict = this;
|
||||||
conflict.changes(function(err, sourceChange, targetChange) {
|
conflict.changes(function(err, sourceChange, targetChange) {
|
||||||
if(err) return cb(err);
|
if (err) return cb(err);
|
||||||
sourceChange.prev = targetChange.rev;
|
sourceChange.prev = targetChange.rev;
|
||||||
sourceChange.save(cb);
|
sourceChange.save(cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the conflict type.
|
* Determine the conflict type.
|
||||||
*
|
*
|
||||||
* Possible results are
|
* Possible results are
|
||||||
|
@ -629,18 +610,19 @@ Conflict.prototype.resolve = function(cb) {
|
||||||
* @param {String} type The conflict type.
|
* @param {String} type The conflict type.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Conflict.prototype.type = function(cb) {
|
Conflict.prototype.type = function(cb) {
|
||||||
var conflict = this;
|
var conflict = this;
|
||||||
this.changes(function(err, sourceChange, targetChange) {
|
this.changes(function(err, sourceChange, targetChange) {
|
||||||
if(err) return cb(err);
|
if (err) return cb(err);
|
||||||
var sourceChangeType = sourceChange.type();
|
var sourceChangeType = sourceChange.type();
|
||||||
var targetChangeType = targetChange.type();
|
var targetChangeType = targetChange.type();
|
||||||
if(sourceChangeType === Change.UPDATE && targetChangeType === Change.UPDATE) {
|
if (sourceChangeType === Change.UPDATE && targetChangeType === Change.UPDATE) {
|
||||||
return cb(null, Change.UPDATE);
|
return cb(null, Change.UPDATE);
|
||||||
}
|
}
|
||||||
if(sourceChangeType === Change.DELETE || targetChangeType === Change.DELETE) {
|
if (sourceChangeType === Change.DELETE || targetChangeType === Change.DELETE) {
|
||||||
return cb(null, Change.DELETE);
|
return cb(null, Change.DELETE);
|
||||||
}
|
}
|
||||||
return cb(null, Change.UNKNOWN);
|
return cb(null, Change.UNKNOWN);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "Change",
|
||||||
|
"trackChanges": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"id": true
|
||||||
|
},
|
||||||
|
"rev": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"checkpoint": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"modelName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"modelId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,9 @@ module.exports = function(loopback) {
|
||||||
require('../common/models/user.json'),
|
require('../common/models/user.json'),
|
||||||
require('../common/models/user.js'));
|
require('../common/models/user.js'));
|
||||||
|
|
||||||
loopback.Change = require('../common/models/change');
|
loopback.Change = createModel(
|
||||||
|
require('../common/models/change.json'),
|
||||||
|
require('../common/models/change.js'));
|
||||||
|
|
||||||
loopback.Checkpoint = createModel(
|
loopback.Checkpoint = createModel(
|
||||||
require('../common/models/checkpoint.json'),
|
require('../common/models/checkpoint.json'),
|
||||||
|
|
|
@ -960,7 +960,10 @@ PersistedModel.enableChangeTracking = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
PersistedModel._defineChangeModel = function() {
|
PersistedModel._defineChangeModel = function() {
|
||||||
var BaseChangeModel = require('./../common/models/change');
|
var BaseChangeModel = loopback.Change;
|
||||||
|
assert(BaseChangeModel,
|
||||||
|
'Change model must be defined before enabling change replication');
|
||||||
|
|
||||||
return this.Change = BaseChangeModel.extend(this.modelName + '-change',
|
return this.Change = BaseChangeModel.extend(this.modelName + '-change',
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue