UNPKG

nqm-minimongo

Version:

Client-side mongo database with server sync over http

525 lines (497 loc) 18.4 kB
var Collection, WebSQLDb, async, compileSort, doNothing, processFind, utils, _; _ = require('lodash'); async = require('async'); utils = require('./utils'); processFind = require('./utils').processFind; compileSort = require('./selector').compileSort; doNothing = function() {}; module.exports = WebSQLDb = (function() { function WebSQLDb(options, success, error) { var checkV2, ex, migrateToV1, migrateToV2; this.collections = {}; try { this.db = window.openDatabase('minimongo_' + options.namespace, '', 'Minimongo:' + options.namespace, 5 * 1024 * 1024); if (!this.db) { return error(new Error("Failed to create database")); } } catch (_error) { ex = _error; if (error) { error(ex); } return; } migrateToV1 = function(tx) { return tx.executeSql('CREATE TABLE docs (\n col TEXT NOT NULL,\n id TEXT NOT NULL,\n state TEXT NOT NULL,\n doc TEXT,\n PRIMARY KEY (col, id));', [], doNothing, (function(tx, err) { return error(err); })); }; migrateToV2 = function(tx) { return tx.executeSql('ALTER TABLE docs ADD COLUMN base TEXT;', [], doNothing, (function(tx, err) { return error(err); })); }; checkV2 = (function(_this) { return function() { if (_this.db.version === "1.0") { return _this.db.changeVersion("1.0", "2.0", migrateToV2, error, function() { if (success) { return success(_this); } }); } else if (_this.db.version !== "2.0") { return error("Unknown db version " + _this.db.version); } else { if (success) { return success(_this); } } }; })(this); if (!this.db.version) { this.db.changeVersion("", "1.0", migrateToV1, error, checkV2); } else { checkV2(); } return this.db; } WebSQLDb.prototype.addCollection = function(name, success, error) { var collection; collection = new Collection(name, this.db); this[name] = collection; this.collections[name] = collection; if (success) { return success(); } }; WebSQLDb.prototype.removeCollection = function(name, success, error) { delete this[name]; delete this.collections[name]; return this.db.transaction(function(tx) { return tx.executeSql("DELETE FROM docs WHERE col = ?", [name], success, (function(tx, err) { return error(err); })); }, error); }; return WebSQLDb; })(); Collection = (function() { function Collection(name, db) { this.name = name; this.db = db; } Collection.prototype.find = function(selector, options) { return { fetch: (function(_this) { return function(success, error) { return _this._findFetch(selector, options, success, error); }; })(this) }; }; Collection.prototype.findOne = function(selector, options, success, error) { var _ref; if (_.isFunction(options)) { _ref = [{}, options, success], options = _ref[0], success = _ref[1], error = _ref[2]; } return this.find(selector, options).fetch(function(results) { if (success != null) { return success(results.length > 0 ? results[0] : null); } }, error); }; Collection.prototype._findFetch = function(selector, options, success, error) { error = error || function() {}; return this.db.readTransaction((function(_this) { return function(tx) { return tx.executeSql("SELECT * FROM docs WHERE col = ?", [_this.name], function(tx, results) { var docs, i, row, _i, _ref; docs = []; for (i = _i = 0, _ref = results.rows.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { row = results.rows.item(i); if (row.state !== "removed") { docs.push(JSON.parse(row.doc)); } } if (success != null) { return success(processFind(docs, selector, options)); } }, (function(tx, err) { return error(err); })); }; })(this), error); }; Collection.prototype.upsert = function(docs, bases, success, error) { var items, _ref; _ref = utils.regularizeUpsert(docs, bases, success, error), items = _ref[0], success = _ref[1], error = _ref[2]; error = error || function() {}; return this.db.transaction((function(_this) { return function(tx) { var ids; ids = _.map(items, function(item) { return item.doc._id; }); bases = {}; return async.eachSeries(ids, function(id, callback) { return tx.executeSql("SELECT * FROM docs WHERE col = ? AND id = ?", [_this.name, id], function(tx2, results) { var row; tx = tx2; if (results.rows.length > 0) { row = results.rows.item(0); if (row.state === "upserted") { bases[row.id] = row.base ? JSON.parse(row.base) : null; } else if (row.state === "cached") { bases[row.id] = JSON.parse(row.doc); } } return callback(); }, (function(tx, err) { return error(err); })); }, function() { var base, id, item, _i, _len, _results; _results = []; for (_i = 0, _len = items.length; _i < _len; _i++) { item = items[_i]; id = item.doc._id; if (item.base !== void 0) { base = item.base; } else if (bases[id]) { base = bases[id]; } else { base = null; } _results.push(tx.executeSql("INSERT OR REPLACE INTO docs (col, id, state, doc, base) VALUES (?, ?, ?, ?, ?)", [_this.name, item.doc._id, "upserted", JSON.stringify(item.doc), JSON.stringify(base)], doNothing, (function(tx, err) { return error(err); }))); } return _results; }); }; })(this), error, function() { if (success) { return success(docs); } }); }; Collection.prototype.remove = function(id, success, error) { if (_.isObject(id)) { this.find(id).fetch((function(_this) { return function(rows) { return async.each(rows, function(row, cb) { return _this.remove(row._id, (function() { return cb(); }), cb); }, function() { return success(); }); }; })(this), error); return; } error = error || function() {}; return this.db.transaction((function(_this) { return function(tx) { return tx.executeSql("SELECT * FROM docs WHERE col = ? AND id = ?", [_this.name, id], function(tx, results) { if (results.rows.length > 0) { return tx.executeSql('UPDATE docs SET state="removed" WHERE col = ? AND id = ?', [_this.name, id], function() { if (success) { return success(id); } }, (function(tx, err) { return error(err); })); } else { return tx.executeSql("INSERT INTO docs (col, id, state, doc) VALUES (?, ?, ?, ?)", [ _this.name, id, "removed", JSON.stringify({ _id: id }) ], function() { if (success) { return success(id); } }, (function(tx, err) { return error(err); })); } }, (function(tx, err) { return error(err); })); }; })(this), error); }; Collection.prototype.cache = function(docs, selector, options, success, error) { error = error || function() {}; return this.db.transaction((function(_this) { return function(tx) { return async.eachSeries(docs, function(doc, callback) { return tx.executeSql("SELECT * FROM docs WHERE col = ? AND id = ?", [_this.name, doc._id], function(tx, results) { var existing; if (results.rows.length === 0 || results.rows.item(0).state === "cached") { existing = results.rows.length > 0 ? JSON.parse(results.rows.item(0).doc) : null; if (!existing || !doc._rev || !existing._rev || doc._rev >= existing._rev) { return tx.executeSql("INSERT OR REPLACE INTO docs (col, id, state, doc) VALUES (?, ?, ?, ?)", [_this.name, doc._id, "cached", JSON.stringify(doc)], function() { return callback(); }, (function(tx, err) { return error(err); })); } else { return callback(); } } else { return callback(); } }, (function(tx, err) { return error(err); })); }, function(err) { var docsMap, sort; if (err) { if (error) { error(err); } return; } docsMap = _.object(_.pluck(docs, "_id"), docs); if (options.sort) { sort = compileSort(options.sort); } return _this.find(selector, options).fetch(function(results) { return _this.db.transaction(function(tx) { return async.eachSeries(results, function(result, callback) { return tx.executeSql("SELECT * FROM docs WHERE col = ? AND id = ?", [_this.name, result._id], function(tx, rows) { if (!docsMap[result._id] && rows.rows.length > 0 && rows.rows.item(0).state === "cached") { if (options.sort && options.limit && docs.length === options.limit) { if (sort(result, _.last(docs)) >= 0) { return callback(); } } return tx.executeSql("DELETE FROM docs WHERE col = ? AND id = ?", [_this.name, result._id], function() { return callback(); }, (function(tx, err) { return error(err); })); } else { return callback(); } }, (function(tx, err) { return error(err); })); }, function(err) { if (err != null) { if (error != null) { error(err); } return; } if (success != null) { return success(); } }); }, error); }, error); }); }; })(this), error); }; Collection.prototype.pendingUpserts = function(success, error) { error = error || function() {}; return this.db.readTransaction((function(_this) { return function(tx) { return tx.executeSql("SELECT * FROM docs WHERE col = ? AND state = ?", [_this.name, "upserted"], function(tx, results) { var docs, i, row, _i, _ref; docs = []; for (i = _i = 0, _ref = results.rows.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { row = results.rows.item(i); docs.push({ doc: JSON.parse(row.doc), base: row.base ? JSON.parse(row.base) : null }); } if (success != null) { return success(docs); } }, (function(tx, err) { return error(err); })); }; })(this), error); }; Collection.prototype.pendingRemoves = function(success, error) { error = error || function() {}; return this.db.readTransaction((function(_this) { return function(tx) { return tx.executeSql("SELECT * FROM docs WHERE col = ? AND state = ?", [_this.name, "removed"], function(tx, results) { var docs, i, row, _i, _ref; docs = []; for (i = _i = 0, _ref = results.rows.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { row = results.rows.item(i); docs.push(JSON.parse(row.doc)._id); } if (success != null) { return success(docs); } }, (function(tx, err) { return error(err); })); }; })(this), error); }; Collection.prototype.resolveUpserts = function(upserts, success, error) { error = error || function() {}; return this.db.transaction((function(_this) { return function(tx) { return async.eachSeries(upserts, function(upsert, cb) { return tx.executeSql("SELECT * FROM docs WHERE col = ? AND id = ?", [_this.name, upsert.doc._id], function(tx, results) { if (results.rows.length > 0 && results.rows.item(0).state === "upserted") { if (_.isEqual(JSON.parse(results.rows.item(0).doc), upsert.doc)) { tx.executeSql('UPDATE docs SET state="cached" WHERE col = ? AND id = ?', [_this.name, upsert.doc._id], doNothing, (function(tx, err) { return error(err); })); return cb(); } else { tx.executeSql('UPDATE docs SET base=? WHERE col = ? AND id = ?', [JSON.stringify(upsert.doc), _this.name, upsert.doc._id], doNothing, (function(tx, err) { return error(err); })); return cb(); } } else { return cb(); } }, (function(tx, err) { return error(err); })); }, function(err) { if (err) { return error(err); } if (success) { return success(); } }); }; })(this), error); }; Collection.prototype.resolveRemove = function(id, success, error) { error = error || function() {}; return this.db.transaction((function(_this) { return function(tx) { return tx.executeSql('DELETE FROM docs WHERE state="removed" AND col = ? AND id = ?', [_this.name, id], function() { if (success) { return success(id); } }, (function(tx, err) { return error(err); })); }; })(this), error); }; Collection.prototype.seed = function(docs, success, error) { if (!_.isArray(docs)) { docs = [docs]; } error = error || function() {}; return this.db.transaction((function(_this) { return function(tx) { return async.eachSeries(docs, function(doc, callback) { return tx.executeSql("SELECT * FROM docs WHERE col = ? AND id = ?", [_this.name, doc._id], function(tx, results) { if (results.rows.length === 0) { return tx.executeSql("INSERT OR REPLACE INTO docs (col, id, state, doc) VALUES (?, ?, ?, ?)", [_this.name, doc._id, "cached", JSON.stringify(doc)], function() { return callback(); }, (function(tx, err) { return error(err); })); } else { return callback(); } }, (function(tx, err) { return error(err); })); }, function(err) { if (err) { if (error) { return error(err); } } else { if (success) { return success(); } } }); }; })(this), error); }; Collection.prototype.cacheOne = function(doc, success, error) { error = error || function() {}; return this.db.transaction((function(_this) { return function(tx) { return tx.executeSql("SELECT * FROM docs WHERE col = ? AND id = ?", [_this.name, doc._id], function(tx, results) { var existing; if (results.rows.length === 0 || results.rows.item(0).state === "cached") { existing = results.rows.length > 0 ? JSON.parse(results.rows.item(0).doc) : null; if (!existing || !doc._rev || !existing._rev || doc._rev >= existing._rev) { return tx.executeSql("INSERT OR REPLACE INTO docs (col, id, state, doc) VALUES (?, ?, ?, ?)", [_this.name, doc._id, "cached", JSON.stringify(doc)], function() { if (success) { return success(doc); } }, (function(tx, err) { return error(err); })); } else { if (success) { return success(doc); } } } else { if (success) { return success(doc); } } }, (function(tx, err) { return error(err); })); }; })(this), error); }; Collection.prototype.uncache = function(selector, success, error) { var compiledSelector; compiledSelector = utils.compileDocumentSelector(selector); error = error || function() {}; return this.db.transaction((function(_this) { return function(tx) { return tx.executeSql("SELECT * FROM docs WHERE col = ? AND state = ?", [_this.name, "cached"], function(tx, results) { var doc, i, row, toRemove, _i, _ref; toRemove = []; for (i = _i = 0, _ref = results.rows.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { row = results.rows.item(i); doc = JSON.parse(row.doc); if (compiledSelector(doc)) { toRemove.push(doc._id); } } return async.eachSeries(toRemove, function(id, callback) { return tx.executeSql('DELETE FROM docs WHERE state="cached" AND col = ? AND id = ?', [_this.name, id], function() { return callback(); }, (function(tx, err) { return error(err); })); }, function(err) { if (err) { if (error) { return error(err); } } else { if (success) { return success(); } } }); }, (function(tx, err) { return error(err); })); }; })(this), error); }; return Collection; })();