| "use strict"; |
| |
| var utils = require('./utils'); |
| var pick = require('./deps/pick'); |
| var toPromise = require('./deps/toPromise'); |
| var collections = require('pouchdb-collections'); |
| var inherits = require('inherits'); |
| var getArguments = require('argsarray'); |
| var adapterFun = require('./deps/adapterFun'); |
| var errors = require('./deps/errors'); |
| var EventEmitter = require('events').EventEmitter; |
| var upsert = require('./deps/upsert'); |
| var Changes = require('./changes'); |
| var bulkGetShim = require('./deps/bulkGetShim'); |
| var Promise = utils.Promise; |
| var isDeleted = require('./deps/docs/isDeleted'); |
| var isLocalId = require('./deps/docs/isLocalId'); |
| var traverseRevTree = require('./deps/merge/traverseRevTree'); |
| var collectLeaves = require('./deps/merge/collectLeaves'); |
| var rootToLeaf = require('./deps/merge/rootToLeaf'); |
| var collectConflicts = require('./deps/merge/collectConflicts'); |
| var parseDoc = require('./deps/docs/parseDoc'); |
| |
| /* |
| * A generic pouch adapter |
| */ |
| |
| function compare(left, right) { |
| return left < right ? -1 : left > right ? 1 : 0; |
| } |
| |
| // returns first element of arr satisfying callback predicate |
| function arrayFirst(arr, callback) { |
| for (var i = 0; i < arr.length; i++) { |
| if (callback(arr[i], i) === true) { |
| return arr[i]; |
| } |
| } |
| } |
| |
| // Wrapper for functions that call the bulkdocs api with a single doc, |
| // if the first result is an error, return an error |
| function yankError(callback) { |
| return function (err, results) { |
| if (err || (results[0] && results[0].error)) { |
| callback(err || results[0]); |
| } else { |
| callback(null, results.length ? results[0] : results); |
| } |
| }; |
| } |
| |
| // clean docs given to us by the user |
| function cleanDocs(docs) { |
| for (var i = 0; i < docs.length; i++) { |
| var doc = docs[i]; |
| if (doc._deleted) { |
| delete doc._attachments; // ignore atts for deleted docs |
| } else if (doc._attachments) { |
| // filter out extraneous keys from _attachments |
| var atts = Object.keys(doc._attachments); |
| for (var j = 0; j < atts.length; j++) { |
| var att = atts[j]; |
| doc._attachments[att] = pick(doc._attachments[att], |
| ['data', 'digest', 'content_type', 'length', 'revpos', 'stub']); |
| } |
| } |
| } |
| } |
| |
| // compare two docs, first by _id then by _rev |
| function compareByIdThenRev(a, b) { |
| var idCompare = compare(a._id, b._id); |
| if (idCompare !== 0) { |
| return idCompare; |
| } |
| var aStart = a._revisions ? a._revisions.start : 0; |
| var bStart = b._revisions ? b._revisions.start : 0; |
| return compare(aStart, bStart); |
| } |
| |
| // for every node in a revision tree computes its distance from the closest |
| // leaf |
| function computeHeight(revs) { |
| var height = {}; |
| var edges = []; |
| traverseRevTree(revs, function (isLeaf, pos, id, prnt) { |
| var rev = pos + "-" + id; |
| if (isLeaf) { |
| height[rev] = 0; |
| } |
| if (prnt !== undefined) { |
| edges.push({from: prnt, to: rev}); |
| } |
| return rev; |
| }); |
| |
| edges.reverse(); |
| edges.forEach(function (edge) { |
| if (height[edge.from] === undefined) { |
| height[edge.from] = 1 + height[edge.to]; |
| } else { |
| height[edge.from] = Math.min(height[edge.from], 1 + height[edge.to]); |
| } |
| }); |
| return height; |
| } |
| |
| function allDocsKeysQuery(api, opts, callback) { |
| var keys = ('limit' in opts) ? |
| opts.keys.slice(opts.skip, opts.limit + opts.skip) : |
| (opts.skip > 0) ? opts.keys.slice(opts.skip) : opts.keys; |
| if (opts.descending) { |
| keys.reverse(); |
| } |
| if (!keys.length) { |
| return api._allDocs({limit: 0}, callback); |
| } |
| var finalResults = { |
| offset: opts.skip |
| }; |
| return Promise.all(keys.map(function (key) { |
| var subOpts = utils.extend({key: key, deleted: 'ok'}, opts); |
| ['limit', 'skip', 'keys'].forEach(function (optKey) { |
| delete subOpts[optKey]; |
| }); |
| return new Promise(function (resolve, reject) { |
| api._allDocs(subOpts, function (err, res) { |
| /* istanbul ignore if */ |
| if (err) { |
| return reject(err); |
| } |
| finalResults.total_rows = res.total_rows; |
| resolve(res.rows[0] || {key: key, error: 'not_found'}); |
| }); |
| }); |
| })).then(function (results) { |
| finalResults.rows = results; |
| return finalResults; |
| }); |
| } |
| |
| // all compaction is done in a queue, to avoid attaching |
| // too many listeners at once |
| function doNextCompaction(self) { |
| var task = self._compactionQueue[0]; |
| var opts = task.opts; |
| var callback = task.callback; |
| self.get('_local/compaction').catch(function () { |
| return false; |
| }).then(function (doc) { |
| if (doc && doc.last_seq) { |
| opts.last_seq = doc.last_seq; |
| } |
| self._compact(opts, function (err, res) { |
| /* istanbul ignore if */ |
| if (err) { |
| callback(err); |
| } else { |
| callback(null, res); |
| } |
| process.nextTick(function () { |
| self._compactionQueue.shift(); |
| if (self._compactionQueue.length) { |
| doNextCompaction(self); |
| } |
| }); |
| }); |
| }); |
| } |
| |
| function attachmentNameError(name) { |
| if (name.charAt(0) === '_') { |
| return name + 'is not a valid attachment name, attachment ' + |
| 'names cannot start with \'_\''; |
| } |
| return false; |
| } |
| |
| inherits(AbstractPouchDB, EventEmitter); |
| module.exports = AbstractPouchDB; |
| |
| function AbstractPouchDB() { |
| EventEmitter.call(this); |
| } |
| |
| AbstractPouchDB.prototype.post = |
| adapterFun('post', function (doc, opts, callback) { |
| if (typeof doc !== 'object' || Array.isArray(doc)) { |
| return callback(errors.error(errors.NOT_AN_OBJECT)); |
| } |
| this.bulkDocs({docs: [doc]}, opts, yankError(callback)); |
| }); |
| |
| AbstractPouchDB.prototype.put = |
| adapterFun('put', function(doc, opts, callback) { |
| if (typeof doc !== 'object' || Array.isArray(doc)) { |
| return callback(errors.error(errors.NOT_AN_OBJECT)); |
| } |
| parseDoc.invalidIdError(doc._id); |
| if (isLocalId(doc._id) && typeof this._putLocal === 'function') { |
| if (doc._deleted) { |
| return this._removeLocal(doc, callback); |
| } else { |
| return this._putLocal(doc, callback); |
| } |
| } |
| this.bulkDocs({docs: [doc]}, opts, yankError(callback)); |
| }); |
| |
| AbstractPouchDB.prototype.putAttachment = |
| adapterFun('putAttachment', function (docId, attachmentId, rev, |
| blob, type, opts, callback) { |
| var api = this; |
| if (typeof opts === 'function') { |
| callback = opts; |
| opts = type; |
| type = blob; |
| blob = rev; |
| rev = null; |
| } |
| |
| function createAttachment(doc) { |
| doc._attachments = doc._attachments || {}; |
| doc._attachments[attachmentId] = { |
| content_type: type, |
| data: blob |
| }; |
| return api.put(doc); |
| } |
| |
| return api.get(docId).then(function (doc) { |
| if (doc._rev !== rev) { |
| throw errors.error(errors.REV_CONFLICT); |
| } |
| |
| return createAttachment(doc); |
| }, function (err) { |
| // create new doc |
| /* istanbul ignore else */ |
| if (err.reason === errors.MISSING_DOC.message) { |
| return createAttachment({_id: docId}); |
| } else { |
| throw err; |
| } |
| }); |
| }); |
| |
| AbstractPouchDB.prototype.removeAttachment = |
| adapterFun('removeAttachment', function (docId, attachmentId, rev, |
| opts, callback) { |
| var self = this; |
| self.get(docId, function (err, obj) { |
| /* istanbul ignore if */ |
| if (err) { |
| callback(err); |
| return; |
| } |
| if (obj._rev !== rev) { |
| callback(errors.error(errors.REV_CONFLICT)); |
| return; |
| } |
| /* istanbul ignore if */ |
| if (!obj._attachments) { |
| return callback(); |
| } |
| delete obj._attachments[attachmentId]; |
| if (Object.keys(obj._attachments).length === 0) { |
| delete obj._attachments; |
| } |
| self.put(obj, callback); |
| }); |
| }); |
| |
| AbstractPouchDB.prototype.remove = |
| adapterFun('remove', function (docOrId, optsOrRev, opts, callback) { |
| var doc; |
| if (typeof optsOrRev === 'string') { |
| // id, rev, opts, callback style |
| doc = { |
| _id: docOrId, |
| _rev: optsOrRev |
| }; |
| } else { |
| // doc, opts, callback style |
| doc = docOrId; |
| callback = opts; |
| opts = optsOrRev; |
| } |
| opts = opts || {}; |
| opts.was_delete = true; |
| var newDoc = {_id: doc._id, _rev: (doc._rev || opts.rev)}; |
| newDoc._deleted = true; |
| if (isLocalId(newDoc._id) && typeof this._removeLocal === 'function') { |
| return this._removeLocal(doc, callback); |
| } |
| this.bulkDocs({docs: [newDoc]}, opts, yankError(callback)); |
| }); |
| |
| AbstractPouchDB.prototype.revsDiff = |
| adapterFun('revsDiff', function (req, opts, callback) { |
| var ids = Object.keys(req); |
| |
| if (!ids.length) { |
| return callback(null, {}); |
| } |
| |
| var count = 0; |
| var missing = new collections.Map(); |
| |
| function addToMissing(id, revId) { |
| if (!missing.has(id)) { |
| missing.set(id, {missing: []}); |
| } |
| missing.get(id).missing.push(revId); |
| } |
| |
| function processDoc(id, rev_tree) { |
| // Is this fast enough? Maybe we should switch to a set simulated by a map |
| var missingForId = req[id].slice(0); |
| traverseRevTree(rev_tree, function (isLeaf, pos, revHash, ctx, |
| opts) { |
| var rev = pos + '-' + revHash; |
| var idx = missingForId.indexOf(rev); |
| if (idx === -1) { |
| return; |
| } |
| |
| missingForId.splice(idx, 1); |
| /* istanbul ignore if */ |
| if (opts.status !== 'available') { |
| addToMissing(id, rev); |
| } |
| }); |
| |
| // Traversing the tree is synchronous, so now `missingForId` contains |
| // revisions that were not found in the tree |
| missingForId.forEach(function (rev) { |
| addToMissing(id, rev); |
| }); |
| } |
| |
| ids.map(function (id) { |
| this._getRevisionTree(id, function (err, rev_tree) { |
| if (err && err.status === 404 && err.message === 'missing') { |
| missing.set(id, {missing: req[id]}); |
| } else if (err) { |
| /* istanbul ignore next */ |
| return callback(err); |
| } else { |
| processDoc(id, rev_tree); |
| } |
| |
| if (++count === ids.length) { |
| // convert LazyMap to object |
| var missingObj = {}; |
| missing.forEach(function (value, key) { |
| missingObj[key] = value; |
| }); |
| return callback(null, missingObj); |
| } |
| }); |
| }, this); |
| }); |
| |
| // _bulk_get API for faster replication, as described in |
| // https://github.com/apache/couchdb-chttpd/pull/33 |
| // At the "abstract" level, it will just run multiple get()s in |
| // parallel, because this isn't much of a performance cost |
| // for local databases (except the cost of multiple transactions, which is |
| // small). The http adapter overrides this in order |
| // to do a more efficient single HTTP request. |
| AbstractPouchDB.prototype.bulkGet = |
| adapterFun('bulkGet', function (opts, callback) { |
| bulkGetShim(this, opts, callback); |
| }); |
| |
| // compact one document and fire callback |
| // by compacting we mean removing all revisions which |
| // are further from the leaf in revision tree than max_height |
| var compactDocument = toPromise(function(self, docId, maxHeight, callback) { |
| self._getRevisionTree(docId, function (err, revTree) { |
| var height = computeHeight(revTree); |
| var candidates = []; |
| var revs = []; |
| Object.keys(height).forEach(function (rev) { |
| if (height[rev] > maxHeight) { |
| candidates.push(rev); |
| } |
| }); |
| |
| traverseRevTree(revTree, function (isLeaf, pos, revHash, ctx, opts) { |
| var rev = pos + '-' + revHash; |
| if (opts.status === 'available' && candidates.indexOf(rev) !== -1) { |
| revs.push(rev); |
| } |
| }); |
| self._doCompaction(docId, revs, callback); |
| }); |
| }); |
| |
| // compact the whole database using single document |
| // compaction |
| AbstractPouchDB.prototype.compact = |
| adapterFun('compact', function (opts, callback) { |
| var self = this; |
| |
| self._compactionQueue = self._compactionQueue || []; |
| self._compactionQueue.push({opts: opts, callback: callback}); |
| if (self._compactionQueue.length === 1) { |
| doNextCompaction(self); |
| } |
| }); |
| AbstractPouchDB.prototype._compact = function (opts, callback) { |
| var self = this; |
| var changesOpts = { |
| returnDocs: false, |
| last_seq: opts.last_seq || 0 |
| }; |
| var promises = []; |
| |
| function onChange(row) { |
| promises.push(compactDocument(self, row.id, 0)); |
| } |
| function onComplete(resp) { |
| var lastSeq = resp.last_seq; |
| Promise.all(promises).then(function () { |
| return upsert(self, '_local/compaction', function deltaFunc(doc) { |
| if (!doc.last_seq || doc.last_seq < lastSeq) { |
| doc.last_seq = lastSeq; |
| return doc; |
| } |
| return false; // somebody else got here first, don't update |
| }); |
| }).then(function () { |
| callback(null, {ok: true}); |
| }).catch(callback); |
| } |
| self.changes(changesOpts) |
| .on('change', onChange) |
| .on('complete', onComplete) |
| .on('error', callback); |
| }; |
| /* Begin api wrappers. Specific functionality to storage belongs in the |
| _[method] */ |
| AbstractPouchDB.prototype.get = |
| adapterFun('get', function (id, opts, callback) { |
| if (typeof id !== 'string') { |
| return callback(errors.error(errors.INVALID_ID)); |
| } |
| if (isLocalId(id) && typeof this._getLocal === 'function') { |
| return this._getLocal(id, callback); |
| } |
| var leaves = [], self = this; |
| |
| function finishOpenRevs() { |
| var result = []; |
| var count = leaves.length; |
| /* istanbul ignore if */ |
| if (!count) { |
| return callback(null, result); |
| } |
| // order with open_revs is unspecified |
| leaves.forEach(function (leaf) { |
| self.get(id, { |
| rev: leaf, |
| revs: opts.revs, |
| attachments: opts.attachments |
| }, function (err, doc) { |
| if (!err) { |
| result.push({ok: doc}); |
| } else { |
| result.push({missing: leaf}); |
| } |
| count--; |
| if (!count) { |
| callback(null, result); |
| } |
| }); |
| }); |
| } |
| |
| if (opts.open_revs) { |
| if (opts.open_revs === "all") { |
| this._getRevisionTree(id, function (err, rev_tree) { |
| if (err) { |
| return callback(err); |
| } |
| leaves = collectLeaves(rev_tree).map(function (leaf) { |
| return leaf.rev; |
| }); |
| finishOpenRevs(); |
| }); |
| } else { |
| if (Array.isArray(opts.open_revs)) { |
| leaves = opts.open_revs; |
| for (var i = 0; i < leaves.length; i++) { |
| var l = leaves[i]; |
| // looks like it's the only thing couchdb checks |
| if (!(typeof(l) === "string" && /^\d+-/.test(l))) { |
| return callback(errors.error(errors.INVALID_REV)); |
| } |
| } |
| finishOpenRevs(); |
| } else { |
| return callback(errors.error(errors.UNKNOWN_ERROR, |
| 'function_clause')); |
| } |
| } |
| return; // open_revs does not like other options |
| } |
| |
| return this._get(id, opts, function (err, result) { |
| if (err) { |
| return callback(err); |
| } |
| |
| var doc = result.doc; |
| var metadata = result.metadata; |
| var ctx = result.ctx; |
| |
| if (opts.conflicts) { |
| var conflicts = collectConflicts(metadata); |
| if (conflicts.length) { |
| doc._conflicts = conflicts; |
| } |
| } |
| |
| if (isDeleted(metadata, doc._rev)) { |
| doc._deleted = true; |
| } |
| |
| if (opts.revs || opts.revs_info) { |
| var paths = rootToLeaf(metadata.rev_tree); |
| var path = arrayFirst(paths, function (arr) { |
| return arr.ids.map(function (x) { return x.id; }) |
| .indexOf(doc._rev.split('-')[1]) !== -1; |
| }); |
| |
| var indexOfRev = path.ids.map(function (x) {return x.id; }) |
| .indexOf(doc._rev.split('-')[1]) + 1; |
| var howMany = path.ids.length - indexOfRev; |
| path.ids.splice(indexOfRev, howMany); |
| path.ids.reverse(); |
| |
| if (opts.revs) { |
| doc._revisions = { |
| start: (path.pos + path.ids.length) - 1, |
| ids: path.ids.map(function (rev) { |
| return rev.id; |
| }) |
| }; |
| } |
| if (opts.revs_info) { |
| var pos = path.pos + path.ids.length; |
| doc._revs_info = path.ids.map(function (rev) { |
| pos--; |
| return { |
| rev: pos + '-' + rev.id, |
| status: rev.opts.status |
| }; |
| }); |
| } |
| } |
| |
| if (opts.attachments && doc._attachments) { |
| var attachments = doc._attachments; |
| var count = Object.keys(attachments).length; |
| if (count === 0) { |
| return callback(null, doc); |
| } |
| Object.keys(attachments).forEach(function (key) { |
| this._getAttachment(attachments[key], { |
| binary: opts.binary, |
| ctx: ctx |
| }, function (err, data) { |
| var att = doc._attachments[key]; |
| att.data = data; |
| delete att.stub; |
| delete att.length; |
| if (!--count) { |
| callback(null, doc); |
| } |
| }); |
| }, self); |
| } else { |
| if (doc._attachments) { |
| for (var key in doc._attachments) { |
| /* istanbul ignore else */ |
| if (doc._attachments.hasOwnProperty(key)) { |
| doc._attachments[key].stub = true; |
| } |
| } |
| } |
| callback(null, doc); |
| } |
| }); |
| }); |
| |
| AbstractPouchDB.prototype.getAttachment = |
| adapterFun('getAttachment', function (docId, attachmentId, opts, |
| callback) { |
| var self = this; |
| this._get(docId, opts, function (err, res) { |
| if (err) { |
| return callback(err); |
| } |
| if (res.doc._attachments && res.doc._attachments[attachmentId]) { |
| opts.ctx = res.ctx; |
| opts.binary = true; |
| self._getAttachment(res.doc._attachments[attachmentId], opts, callback); |
| } else { |
| return callback(errors.error(errors.MISSING_DOC)); |
| } |
| }); |
| }); |
| |
| AbstractPouchDB.prototype.allDocs = |
| adapterFun('allDocs', function (opts, callback) { |
| opts.skip = typeof opts.skip !== 'undefined' ? opts.skip : 0; |
| if (opts.start_key) { |
| opts.startkey = opts.start_key; |
| } |
| if (opts.end_key) { |
| opts.endkey = opts.end_key; |
| } |
| if ('keys' in opts) { |
| if (!Array.isArray(opts.keys)) { |
| return callback(new TypeError('options.keys must be an array')); |
| } |
| var incompatibleOpt = |
| ['startkey', 'endkey', 'key'].filter(function (incompatibleOpt) { |
| return incompatibleOpt in opts; |
| })[0]; |
| if (incompatibleOpt) { |
| callback(errors.error(errors.QUERY_PARSE_ERROR, |
| 'Query parameter `' + incompatibleOpt + |
| '` is not compatible with multi-get' |
| )); |
| return; |
| } |
| if (this.type() !== 'http') { |
| return allDocsKeysQuery(this, opts, callback); |
| } |
| } |
| |
| return this._allDocs(opts, callback); |
| }); |
| |
| AbstractPouchDB.prototype.changes = function (opts, callback) { |
| if (typeof opts === 'function') { |
| callback = opts; |
| opts = {}; |
| } |
| return new Changes(this, opts, callback); |
| }; |
| |
| AbstractPouchDB.prototype.close = |
| adapterFun('close', function (opts, callback) { |
| this._closed = true; |
| return this._close(callback); |
| }); |
| |
| AbstractPouchDB.prototype.info = adapterFun('info', function (opts, callback) { |
| var self = this; |
| this._info(function (err, info) { |
| if (err) { |
| return callback(err); |
| } |
| // assume we know better than the adapter, unless it informs us |
| info.db_name = info.db_name || self._db_name; |
| info.auto_compaction = !!(self.auto_compaction && self.type() !== 'http'); |
| info.adapter = self.type(); |
| callback(null, info); |
| }); |
| }); |
| |
| AbstractPouchDB.prototype.id = adapterFun('id', function (opts, callback) { |
| return this._id(callback); |
| }); |
| |
| AbstractPouchDB.prototype.type = function () { |
| /* istanbul ignore next */ |
| return (typeof this._type === 'function') ? this._type() : this.adapter; |
| }; |
| |
| AbstractPouchDB.prototype.bulkDocs = |
| adapterFun('bulkDocs', function (req, opts, callback) { |
| if (Array.isArray(req)) { |
| req = { |
| docs: req |
| }; |
| } |
| |
| if (!req || !req.docs || !Array.isArray(req.docs)) { |
| return callback(errors.error(errors.MISSING_BULK_DOCS)); |
| } |
| |
| for (var i = 0; i < req.docs.length; ++i) { |
| if (typeof req.docs[i] !== 'object' || Array.isArray(req.docs[i])) { |
| return callback(errors.error(errors.NOT_AN_OBJECT)); |
| } |
| } |
| |
| var attachmentError; |
| req.docs.forEach(function(doc) { |
| if (doc._attachments) { |
| Object.keys(doc._attachments).forEach(function (name) { |
| attachmentError = attachmentError || attachmentNameError(name); |
| }); |
| } |
| }); |
| |
| if (attachmentError) { |
| return callback(errors.error(errors.BAD_REQUEST, attachmentError)); |
| } |
| |
| if (!('new_edits' in opts)) { |
| if ('new_edits' in req) { |
| opts.new_edits = req.new_edits; |
| } else { |
| opts.new_edits = true; |
| } |
| } |
| |
| if (!opts.new_edits && this.type() !== 'http') { |
| // ensure revisions of the same doc are sorted, so that |
| // the local adapter processes them correctly (#2935) |
| req.docs.sort(compareByIdThenRev); |
| } |
| |
| cleanDocs(req.docs); |
| |
| return this._bulkDocs(req, opts, function (err, res) { |
| if (err) { |
| return callback(err); |
| } |
| if (!opts.new_edits) { |
| // this is what couch does when new_edits is false |
| res = res.filter(function (x) { |
| return x.error; |
| }); |
| } |
| callback(null, res); |
| }); |
| }); |
| |
| AbstractPouchDB.prototype.registerDependentDatabase = |
| adapterFun('registerDependentDatabase', function (dependentDb, |
| opts, callback) { |
| var depDB = new this.constructor(dependentDb, this.__opts); |
| |
| function diffFun(doc) { |
| doc.dependentDbs = doc.dependentDbs || {}; |
| if (doc.dependentDbs[dependentDb]) { |
| return false; // no update required |
| } |
| doc.dependentDbs[dependentDb] = true; |
| return doc; |
| } |
| upsert(this, '_local/_pouch_dependentDbs', diffFun) |
| .then(function () { |
| callback(null, {db: depDB}); |
| }).catch(callback); |
| }); |
| |
| AbstractPouchDB.prototype.destroy = |
| adapterFun('destroy', function (opts, callback) { |
| var self = this; |
| var usePrefix = 'use_prefix' in self ? self.use_prefix : true; |
| |
| function destroyDb() { |
| // call destroy method of the particular adaptor |
| self._destroy(opts, function (err, resp) { |
| if (err) { |
| return callback(err); |
| } |
| self.emit('destroyed'); |
| callback(null, resp || { 'ok': true }); |
| }); |
| } |
| |
| if (self.type() === 'http') { |
| // no need to check for dependent DBs if it's a remote DB |
| return destroyDb(); |
| } |
| |
| self.get('_local/_pouch_dependentDbs', function (err, localDoc) { |
| if (err) { |
| /* istanbul ignore if */ |
| if (err.status !== 404) { |
| return callback(err); |
| } else { // no dependencies |
| return destroyDb(); |
| } |
| } |
| var dependentDbs = localDoc.dependentDbs; |
| var PouchDB = self.constructor; |
| var deletedMap = Object.keys(dependentDbs).map(function (name) { |
| var trueName = usePrefix ? |
| name.replace(new RegExp('^' + PouchDB.prefix), '') : name; |
| return new PouchDB(trueName, self.__opts).destroy(); |
| }); |
| Promise.all(deletedMap).then(destroyDb, function (error) { |
| /* istanbul ignore next */ |
| callback(error); |
| }); |
| }); |
| }); |