nqm-minimongo
Version:
Client-side mongo database with server sync over http
361 lines (338 loc) • 11.3 kB
JavaScript
/*
Database which caches locally in a localDb but pulls results
ultimately from a RemoteDb
*/
var HybridCollection, HybridDb, processFind, utils, _;
_ = require('lodash');
processFind = require('./utils').processFind;
utils = require('./utils');
module.exports = HybridDb = (function() {
function HybridDb(localDb, remoteDb) {
this.localDb = localDb;
this.remoteDb = remoteDb;
this.collections = {};
}
HybridDb.prototype.addCollection = function(name, options, success, error) {
var collection, _ref;
if (_.isFunction(options)) {
_ref = [{}, options, success], options = _ref[0], success = _ref[1], error = _ref[2];
}
collection = new HybridCollection(name, this.localDb[name], this.remoteDb[name], options);
this[name] = collection;
this.collections[name] = collection;
if (success != null) {
return success();
}
};
HybridDb.prototype.removeCollection = function(name, success, error) {
delete this[name];
delete this.collections[name];
if (success != null) {
return success();
}
};
HybridDb.prototype.upload = function(success, error) {
var cols, uploadCols;
cols = _.values(this.collections);
uploadCols = function(cols, success, error) {
var col;
col = _.first(cols);
if (col) {
return col.upload(function() {
return uploadCols(_.rest(cols), success, error);
}, function(err) {
return error(err);
});
} else {
return success();
}
};
return uploadCols(cols, success, error);
};
return HybridDb;
})();
HybridCollection = (function() {
function HybridCollection(name, localCol, remoteCol, options) {
this.name = name;
this.localCol = localCol;
this.remoteCol = remoteCol;
this.options = options || {};
_.defaults(this.options, {
cacheFind: true,
cacheFindOne: true,
interim: true,
useLocalOnRemoteError: true,
shortcut: false,
timeout: 0,
sortUpserts: null
});
}
HybridCollection.prototype.find = function(selector, options) {
if (options == null) {
options = {};
}
return {
fetch: (function(_this) {
return function(success, error) {
return _this._findFetch(selector, options, success, error);
};
})(this)
};
};
HybridCollection.prototype.findOne = function(selector, options, success, error) {
var step2, _ref;
if (options == null) {
options = {};
}
if (_.isFunction(options)) {
_ref = [{}, options, success], options = _ref[0], success = _ref[1], error = _ref[2];
}
_.defaults(options, this.options);
step2 = (function(_this) {
return function(localDoc) {
var findOptions;
findOptions = _.cloneDeep(options);
findOptions.interim = false;
findOptions.cacheFind = options.cacheFindOne;
if (selector._id) {
findOptions.limit = 1;
} else {
delete findOptions.limit;
}
return _this.find(selector, findOptions).fetch(function(data) {
if (data.length > 0) {
if (!_.isEqual(localDoc, data[0])) {
return success(data[0]);
}
} else {
return success(null);
}
}, error);
};
})(this);
if (options.interim || options.shortcut) {
return this.localCol.findOne(selector, options, function(localDoc) {
if (localDoc) {
success(_.cloneDeep(localDoc));
if (options.shortcut) {
return;
}
}
return step2(localDoc);
}, error);
} else {
return step2();
}
};
HybridCollection.prototype._findFetch = function(selector, options, success, error) {
var localSuccess, step2;
_.defaults(options, this.options);
step2 = (function(_this) {
return function(localData) {
var remoteError, remoteOptions, remoteSuccess, timedOut, timer;
remoteOptions = _.cloneDeep(options);
if (options.cacheFind) {
delete remoteOptions.fields;
}
timer = null;
timedOut = false;
remoteSuccess = function(remoteData) {
var cacheSuccess, data;
if (timer) {
clearTimeout(timer);
}
if (timedOut) {
if (options.cacheFind) {
_this.localCol.cache(remoteData, selector, options, (function() {}), error);
}
return;
}
if (options.cacheFind) {
cacheSuccess = function() {
var localSuccess2;
localSuccess2 = function(localData2) {
if (!_.isEqual(localData, localData2)) {
return success(localData2);
}
};
return _this.localCol.find(selector, options).fetch(localSuccess2, error);
};
return _this.localCol.cache(remoteData, selector, options, cacheSuccess, error);
} else {
data = remoteData;
return _this.localCol.pendingRemoves(function(removes) {
var removesMap;
if (removes.length > 0) {
removesMap = _.object(_.map(removes, function(id) {
return [id, id];
}));
data = _.filter(remoteData, function(doc) {
return !_.has(removesMap, doc._id);
});
}
return _this.localCol.pendingUpserts(function(upserts) {
var upsertsMap;
if (upserts.length > 0) {
upsertsMap = _.object(_.map(upserts, function(u) {
return u.doc._id;
}), _.map(upserts, function(u) {
return u.doc._id;
}));
data = _.filter(data, function(doc) {
return !_.has(upsertsMap, doc._id);
});
data = data.concat(_.pluck(upserts, "doc"));
data = processFind(data, selector, options);
}
if (!_.isEqual(localData, data)) {
return success(data);
}
}, error);
}, error);
}
};
remoteError = function(err) {
if (timer) {
clearTimeout(timer);
}
if (timedOut) {
return;
}
if (!options.interim) {
if (options.useLocalOnRemoteError) {
return _this.localCol.find(selector, options).fetch(success, error);
} else {
if (error) {
return error(err);
}
}
} else {
}
};
if (options.timeout) {
timer = setTimeout(function() {
timer = null;
timedOut = true;
if (!options.interim) {
if (options.useLocalOnRemoteError) {
return _this.localCol.find(selector, options).fetch(success, error);
} else {
if (error) {
return error(new Error("Remote timed out"));
}
}
} else {
}
}, options.timeout);
}
return _this.remoteCol.find(selector, remoteOptions).fetch(remoteSuccess, remoteError);
};
})(this);
if (options.interim) {
localSuccess = function(localData) {
success(localData);
return step2(localData);
};
return this.localCol.find(selector, options).fetch(localSuccess, error);
} else {
return step2();
}
};
HybridCollection.prototype.upsert = function(docs, bases, success, error) {
return this.localCol.upsert(docs, bases, function(result) {
if (_.isFunction(bases)) {
success = bases;
}
return typeof success === "function" ? success(docs) : void 0;
}, error);
};
HybridCollection.prototype.remove = function(id, success, error) {
return this.localCol.remove(id, function() {
if (success != null) {
return success();
}
}, error);
};
HybridCollection.prototype.upload = function(success, error) {
var uploadRemoves, uploadUpserts;
uploadUpserts = (function(_this) {
return function(upserts, success, error) {
var upsert;
upsert = _.first(upserts);
if (upsert) {
return _this.remoteCol.upsert(upsert.doc, upsert.base, function(remoteDoc) {
return _this.localCol.resolveUpserts([upsert], function() {
if (remoteDoc) {
return _this.localCol.cacheOne(remoteDoc, function() {
return uploadUpserts(_.rest(upserts), success, error);
}, error);
} else {
return _this.localCol.remove(upsert.doc._id, function() {
return _this.localCol.resolveRemove(upsert.doc._id, function() {
return uploadUpserts(_.rest(upserts), success, error);
}, error);
}, error);
}
}, error);
}, function(err) {
if (err.status === 410 || err.status === 403) {
return _this.localCol.remove(upsert.doc._id, function() {
return _this.localCol.resolveRemove(upsert.doc._id, function() {
if (err.status === 410) {
return uploadUpserts(_.rest(upserts), success, error);
} else {
return error(err);
}
}, error);
}, error);
} else {
return error(err);
}
});
} else {
return success();
}
};
})(this);
uploadRemoves = (function(_this) {
return function(removes, success, error) {
var remove;
remove = _.first(removes);
if (remove) {
return _this.remoteCol.remove(remove, function() {
return _this.localCol.resolveRemove(remove, function() {
return uploadRemoves(_.rest(removes), success, error);
}, error);
}, function(err) {
if (err.status === 410 || err.status === 403) {
return _this.localCol.resolveRemove(remove, function() {
if (err.status === 410) {
return uploadRemoves(_.rest(removes), success, error);
} else {
return error(err);
}
}, error);
} else {
return error(err);
}
}, error);
} else {
return success();
}
};
})(this);
return this.localCol.pendingUpserts((function(_this) {
return function(upserts) {
if (_this.options.sortUpserts) {
upserts.sort(_this.options.sortUpserts);
}
return uploadUpserts(upserts, function() {
return _this.localCol.pendingRemoves(function(removes) {
return uploadRemoves(removes, success, error);
}, error);
}, error);
};
})(this), error);
};
return HybridCollection;
})();