diff --git a/lib/create-or-delete-dbs.js b/lib/create-or-delete-dbs.js new file mode 100644 index 0000000..3b034e8 --- /dev/null +++ b/lib/create-or-delete-dbs.js @@ -0,0 +1,52 @@ +'use strict'; + +// unified access to creating/deleting PouchDB databases, +// to re-use PouchDB objects and avoid race conditions + +var Promise = require('pouchdb-promise'); +var PouchMap = require('pouchdb-collections').Map; + +var promiseChain = Promise.resolve(); + +// do all creations/deletions in a single global lock +// obviously this makes this operation slow... but hopefully you aren't +// creating and destroying a lot of databases all the time +function doSequentially(fun) { + promiseChain = promiseChain.then(fun); + return promiseChain; +} + +function getOrCreateDB(PouchDB, dbName) { + var map = PouchDB.__dbCacheMap; + if (!map) { + // cache DBs to avoid costly re-allocations + map = PouchDB.__dbCacheMap = new PouchMap(); + } + return doSequentially(function () { + var db = map.get(dbName); + if (db) { + return db; + } + db = new PouchDB(dbName); + map.set(dbName, db); + return db; + }); +} + +function deleteDB(PouchDB, dbName, opts) { + return doSequentially(function () { + var dbCache = PouchDB.__dbCacheMap; + var db = dbCache && dbCache.get(dbName); + if (!db) { + // if the db wasn't cached, we may still need to destroy it + // if it's saved to disk + return PouchDB.destroy(dbName, opts); + } + // if this db was cached, we need to remove it from the cache immediately + dbCache.delete(dbName); + return db.destroy(opts); + }); +} + +exports.getOrCreateDB = getOrCreateDB; +exports.deleteDB = deleteDB; \ No newline at end of file diff --git a/lib/db-wrapper.js b/lib/db-wrapper.js index 5eaaee2..5506fde 100644 --- a/lib/db-wrapper.js +++ b/lib/db-wrapper.js @@ -19,10 +19,13 @@ DatabaseWrapper.prototype.wrap = function (name, db) { if (typeof db === 'undefined') { throw new Error("no db defined!"); } + if (db.__wrappedByExpressPouch) { + // dbs are cached, so it might already be wrapped + return Promise.resolve(db); + } + db.__wrappedByExpressPouch = true; return utils.callAsyncRecursive(this._wrappers, function (wrapper, next) { - return Promise.resolve().then(function () { - return wrapper(name, db, next); - }); + return Promise.resolve(wrapper(name, db, next)); }).then(function () { // https://github.com/pouchdb/pouchdb/issues/1940 delete db.then; diff --git a/lib/replicator.js b/lib/replicator.js index 04ccd8d..d44eefd 100644 --- a/lib/replicator.js +++ b/lib/replicator.js @@ -2,6 +2,7 @@ var Promise = require('pouchdb-promise'); var utils = require('./utils'); +var getOrCreateDB = require('./create-or-delete-dbs').getOrCreateDB; module.exports = function enableReplicator(app) { var db, PouchDB; @@ -55,10 +56,10 @@ module.exports = function enableReplicator(app) { return serialExecution(function () { var name = getReplicatorDBName(); - db = new PouchDB(name); - - PouchDB.on('destroyed', onDestroy); - return db.startReplicatorDaemon(); + return getOrCreateDB(PouchDB, name).then(function (db) { + PouchDB.on('destroyed', onDestroy); + return db.startReplicatorDaemon(); + }); }); }, stop: function () { diff --git a/lib/routes/db.js b/lib/routes/db.js index c25e0cf..cabf276 100644 --- a/lib/routes/db.js +++ b/lib/routes/db.js @@ -5,7 +5,8 @@ var startTime = new Date().getTime(), wrappers = require('pouchdb-wrappers'), mkdirp = require('mkdirp'), pathResolve = require('path').resolve, - cleanFilename = require('../clean-filename'); + cleanFilename = require('../clean-filename'), + deleteDB = require('../create-or-delete-dbs').deleteDB; module.exports = function (app) { // Create a database. @@ -63,7 +64,7 @@ module.exports = function (app) { }); // Delete a database - app.delete('/:db', function (req, res, next) { + app.delete('/:db', function (req, res) { if (req.query.rev) { return utils.sendJSON(res, 400, { error: 'bad_request', @@ -73,13 +74,12 @@ module.exports = function (app) { ) }); } - var name = cleanFilename(req.params.db); - req.PouchDB.destroy(name, utils.makeOpts(req), function (err, info) { - if (err) { - return utils.sendError(res, err); - } + var dbName = cleanFilename(req.params.db); + deleteDB(req.PouchDB, dbName, utils.makeOpts(req)).then(function () { utils.sendJSON(res, 200, {ok: true}); + }, function (err) { + utils.sendError(res, err); }); }); diff --git a/lib/utils.js b/lib/utils.js index 0755830..e46b326 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -5,6 +5,7 @@ var rawBody = require('raw-body'); var Promise = require('pouchdb-promise'); var mkdirp = require('mkdirp'); var cleanFilename = require('./clean-filename'); +var getOrCreateDB = require('./create-or-delete-dbs').getOrCreateDB; //shared middleware @@ -75,18 +76,18 @@ exports.setDBOnReq = function (dbName, dbWrapper, req, res, next) { reason: 'no_db_file' }); } - var db = new req.PouchDB(dbName); - - // temporary workaround for https://github.com/pouchdb/pouchdb/issues/5668 - // see also https://github.com/pouchdb/express-pouchdb/issues/274 - if (/\//.test(dbName)) { - var path = db.__opts.prefix ? db.__opts.prefix + dbName : dbName; - mkdirp.sync(pathResolve(path)); - } + getOrCreateDB(req.PouchDB, dbName).then(function (db) { + // temporary workaround for https://github.com/pouchdb/pouchdb/issues/5668 + // see also https://github.com/pouchdb/express-pouchdb/issues/274 + if (/\//.test(dbName)) { + var path = db.__opts.prefix ? db.__opts.prefix + dbName : dbName; + mkdirp.sync(pathResolve(path)); + } - dbWrapper.wrap(dbName, db).then(function () { - req.db = db; - next(); + dbWrapper.wrap(dbName, db).then(function () { + req.db = db; + next(); + }); }); }); }; diff --git a/package.json b/package.json index 836ff60..06b20ff 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "on-finished": "^2.3.0", "pouchdb-all-dbs": "^1.0.2", "pouchdb-auth": "^2.1.1", + "pouchdb-collections": "6.1.2", "pouchdb-find": "^0.10.3", "pouchdb-list": "^1.1.0", "pouchdb-promise": "6.1.2",