Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

pouchdb-core

Package Overview
Dependencies
Maintainers
9
Versions
39
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pouchdb-core - npm Package Compare versions

Comparing version 7.3.1 to 8.0.0

1997

lib/index.es.js
import { Map } from 'pouchdb-collections';
import getArguments from 'argsarray';
import { isDeleted, collectLeaves, collectConflicts, traverseRevTree, rootToLeaf, isLocalId } from 'pouchdb-merge';
import { isDeleted, collectLeaves, collectConflicts, traverseRevTree, rootToLeaf, isLocalId, findPathToLeaf } from 'pouchdb-merge';
import { MISSING_BULK_DOCS, MISSING_DOC, REV_CONFLICT, INVALID_ID, UNKNOWN_ERROR, QUERY_PARSE_ERROR, BAD_REQUEST, NOT_AN_OBJECT, INVALID_REV, createError } from 'pouchdb-errors';
import { clone, listenerCount, once, guardedConsole, rev, isRemote, pick, adapterFun, upsert, bulkGetShim, invalidIdError, nextTick, hasLocalStorage, assign } from 'pouchdb-utils';
import inherits from 'inherits';
import EE from 'events';
import { fetch } from 'pouchdb-fetch';
import { v4 } from 'uuid';
import pouchChangesFilter from 'pouchdb-changes-filter';
inherits(Changes, EE);
function tryCatchInChangeListener(self, change, pending, lastSeq) {

@@ -22,78 +19,2 @@ // isolate try/catches to avoid V8 deoptimizations

function Changes(db, opts, callback) {
EE.call(this);
var self = this;
this.db = db;
opts = opts ? clone(opts) : {};
var complete = opts.complete = once(function (err, resp) {
if (err) {
if (listenerCount(self, 'error') > 0) {
self.emit('error', err);
}
} else {
self.emit('complete', resp);
}
self.removeAllListeners();
db.removeListener('destroyed', onDestroy);
});
if (callback) {
self.on('complete', function (resp) {
callback(null, resp);
});
self.on('error', callback);
}
function onDestroy() {
self.cancel();
}
db.once('destroyed', onDestroy);
opts.onChange = function (change, pending, lastSeq) {
/* istanbul ignore if */
if (self.isCancelled) {
return;
}
tryCatchInChangeListener(self, change, pending, lastSeq);
};
var promise = new Promise(function (fulfill, reject) {
opts.complete = function (err, res) {
if (err) {
reject(err);
} else {
fulfill(res);
}
};
});
self.once('cancel', function () {
db.removeListener('destroyed', onDestroy);
opts.complete(null, {status: 'cancelled'});
});
this.then = promise.then.bind(promise);
this['catch'] = promise['catch'].bind(promise);
this.then(function (result) {
complete(null, result);
}, complete);
if (!db.taskqueue.isReady) {
db.taskqueue.addTask(function (failed) {
if (failed) {
opts.complete(failed);
} else if (self.isCancelled) {
self.emit('cancel');
} else {
self.validateChanges(opts);
}
});
} else {
self.validateChanges(opts);
}
}
Changes.prototype.cancel = function () {
this.isCancelled = true;
if (this.db.taskqueue.isReady) {
this.emit('cancel');
}
};
function processChange(doc, metadata, opts) {

@@ -123,83 +44,160 @@ var changeList = [{rev: doc._rev}];

Changes.prototype.validateChanges = function (opts) {
var callback = opts.complete;
var self = this;
/* istanbul ignore else */
if (PouchDB._changesFilterPlugin) {
PouchDB._changesFilterPlugin.validate(opts, function (err) {
class Changes extends EE {
constructor(db, opts, callback) {
super();
this.db = db;
opts = opts ? clone(opts) : {};
var complete = opts.complete = once((err, resp) => {
if (err) {
return callback(err);
if (listenerCount(this, 'error') > 0) {
this.emit('error', err);
}
} else {
this.emit('complete', resp);
}
self.doChanges(opts);
this.removeAllListeners();
db.removeListener('destroyed', onDestroy);
});
} else {
self.doChanges(opts);
}
};
Changes.prototype.doChanges = function (opts) {
var self = this;
var callback = opts.complete;
opts = clone(opts);
if ('live' in opts && !('continuous' in opts)) {
opts.continuous = opts.live;
}
opts.processChange = processChange;
if (opts.since === 'latest') {
opts.since = 'now';
}
if (!opts.since) {
opts.since = 0;
}
if (opts.since === 'now') {
this.db.info().then(function (info) {
if (callback) {
this.on('complete', function (resp) {
callback(null, resp);
});
this.on('error', callback);
}
const onDestroy = () => {
this.cancel();
};
db.once('destroyed', onDestroy);
opts.onChange = (change, pending, lastSeq) => {
/* istanbul ignore if */
if (self.isCancelled) {
callback(null, {status: 'cancelled'});
if (this.isCancelled) {
return;
}
opts.since = info.update_seq;
self.doChanges(opts);
}, callback);
return;
tryCatchInChangeListener(this, change, pending, lastSeq);
};
var promise = new Promise(function (fulfill, reject) {
opts.complete = function (err, res) {
if (err) {
reject(err);
} else {
fulfill(res);
}
};
});
this.once('cancel', function () {
db.removeListener('destroyed', onDestroy);
opts.complete(null, {status: 'cancelled'});
});
this.then = promise.then.bind(promise);
this['catch'] = promise['catch'].bind(promise);
this.then(function (result) {
complete(null, result);
}, complete);
if (!db.taskqueue.isReady) {
db.taskqueue.addTask((failed) => {
if (failed) {
opts.complete(failed);
} else if (this.isCancelled) {
this.emit('cancel');
} else {
this.validateChanges(opts);
}
});
} else {
this.validateChanges(opts);
}
}
/* istanbul ignore else */
if (PouchDB._changesFilterPlugin) {
PouchDB._changesFilterPlugin.normalize(opts);
if (PouchDB._changesFilterPlugin.shouldFilter(this, opts)) {
return PouchDB._changesFilterPlugin.filter(this, opts);
cancel() {
this.isCancelled = true;
if (this.db.taskqueue.isReady) {
this.emit('cancel');
}
} else {
['doc_ids', 'filter', 'selector', 'view'].forEach(function (key) {
if (key in opts) {
guardedConsole('warn',
'The "' + key + '" option was passed in to changes/replicate, ' +
'but pouchdb-changes-filter plugin is not installed, so it ' +
'was ignored. Please install the plugin to enable filtering.'
);
}
});
}
if (!('descending' in opts)) {
opts.descending = false;
validateChanges(opts) {
var callback = opts.complete;
/* istanbul ignore else */
if (PouchDB._changesFilterPlugin) {
PouchDB._changesFilterPlugin.validate(opts, (err) => {
if (err) {
return callback(err);
}
this.doChanges(opts);
});
} else {
this.doChanges(opts);
}
}
// 0 and 1 should return 1 document
opts.limit = opts.limit === 0 ? 1 : opts.limit;
opts.complete = callback;
var newPromise = this.db._changes(opts);
/* istanbul ignore else */
if (newPromise && typeof newPromise.cancel === 'function') {
var cancel = self.cancel;
self.cancel = getArguments(function (args) {
newPromise.cancel();
cancel.apply(this, args);
});
doChanges(opts) {
var callback = opts.complete;
opts = clone(opts);
if ('live' in opts && !('continuous' in opts)) {
opts.continuous = opts.live;
}
opts.processChange = processChange;
if (opts.since === 'latest') {
opts.since = 'now';
}
if (!opts.since) {
opts.since = 0;
}
if (opts.since === 'now') {
this.db.info().then((info) => {
/* istanbul ignore if */
if (this.isCancelled) {
callback(null, {status: 'cancelled'});
return;
}
opts.since = info.update_seq;
this.doChanges(opts);
}, callback);
return;
}
/* istanbul ignore else */
if (PouchDB._changesFilterPlugin) {
PouchDB._changesFilterPlugin.normalize(opts);
if (PouchDB._changesFilterPlugin.shouldFilter(this, opts)) {
return PouchDB._changesFilterPlugin.filter(this, opts);
}
} else {
['doc_ids', 'filter', 'selector', 'view'].forEach(function (key) {
if (key in opts) {
guardedConsole('warn',
'The "' + key + '" option was passed in to changes/replicate, ' +
'but pouchdb-changes-filter plugin is not installed, so it ' +
'was ignored. Please install the plugin to enable filtering.'
);
}
});
}
if (!('descending' in opts)) {
opts.descending = false;
}
// 0 and 1 should return 1 document
opts.limit = opts.limit === 0 ? 1 : opts.limit;
opts.complete = callback;
var newPromise = this.db._changes(opts);
/* istanbul ignore else */
if (newPromise && typeof newPromise.cancel === 'function') {
const cancel = this.cancel;
this.cancel = (...args) => {
newPromise.cancel();
cancel.apply(this, args);
};
}
}
};
}

@@ -326,2 +324,33 @@ /*

function appendPurgeSeq(db, docId, rev$$1) {
return db.get('_local/purges').then(function (doc) {
const purgeSeq = doc.purgeSeq + 1;
doc.purges.push({
docId,
rev: rev$$1,
purgeSeq,
});
if (doc.purges.length > self.purged_infos_limit) {
doc.purges.splice(0, doc.purges.length - self.purged_infos_limit);
}
doc.purgeSeq = purgeSeq;
return doc;
}).catch(function (err) {
if (err.status !== 404) {
throw err;
}
return {
_id: '_local/purges',
purges: [{
docId,
rev: rev$$1,
purgeSeq: 0,
}],
purgeSeq: 0,
};
}).then(function (doc) {
return db.put(doc);
});
}
function attachmentNameError(name) {

@@ -335,849 +364,870 @@ if (name.charAt(0) === '_') {

inherits(AbstractPouchDB, EE);
class AbstractPouchDB extends EE {
_setup() {
this.post = adapterFun('post', function (doc, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
if (typeof doc !== 'object' || Array.isArray(doc)) {
return callback(createError(NOT_AN_OBJECT));
}
this.bulkDocs({docs: [doc]}, opts, yankError(callback, doc._id));
}).bind(this);
function AbstractPouchDB() {
EE.call(this);
this.put = adapterFun('put', function (doc, opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
if (typeof doc !== 'object' || Array.isArray(doc)) {
return cb(createError(NOT_AN_OBJECT));
}
invalidIdError(doc._id);
if (isLocalId(doc._id) && typeof this._putLocal === 'function') {
if (doc._deleted) {
return this._removeLocal(doc, cb);
} else {
return this._putLocal(doc, cb);
}
}
// re-bind prototyped methods
for (var p in AbstractPouchDB.prototype) {
if (typeof this[p] === 'function') {
this[p] = this[p].bind(this);
}
}
}
const putDoc = (next) => {
if (typeof this._put === 'function' && opts.new_edits !== false) {
this._put(doc, opts, next);
} else {
this.bulkDocs({docs: [doc]}, opts, yankError(next, doc._id));
}
};
AbstractPouchDB.prototype.post =
adapterFun('post', function (doc, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
if (typeof doc !== 'object' || Array.isArray(doc)) {
return callback(createError(NOT_AN_OBJECT));
}
this.bulkDocs({docs: [doc]}, opts, yankError(callback, doc._id));
});
if (opts.force && doc._rev) {
transformForceOptionToNewEditsOption();
putDoc(function (err) {
var result = err ? null : {ok: true, id: doc._id, rev: doc._rev};
cb(err, result);
});
} else {
putDoc(cb);
}
AbstractPouchDB.prototype.put = adapterFun('put', function (doc, opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
if (typeof doc !== 'object' || Array.isArray(doc)) {
return cb(createError(NOT_AN_OBJECT));
}
invalidIdError(doc._id);
if (isLocalId(doc._id) && typeof this._putLocal === 'function') {
if (doc._deleted) {
return this._removeLocal(doc, cb);
} else {
return this._putLocal(doc, cb);
}
}
var self = this;
if (opts.force && doc._rev) {
transformForceOptionToNewEditsOption();
putDoc(function (err) {
var result = err ? null : {ok: true, id: doc._id, rev: doc._rev};
cb(err, result);
});
} else {
putDoc(cb);
}
function transformForceOptionToNewEditsOption() {
var parts = doc._rev.split('-');
var oldRevId = parts[1];
var oldRevNum = parseInt(parts[0], 10);
function transformForceOptionToNewEditsOption() {
var parts = doc._rev.split('-');
var oldRevId = parts[1];
var oldRevNum = parseInt(parts[0], 10);
var newRevNum = oldRevNum + 1;
var newRevId = rev();
var newRevNum = oldRevNum + 1;
var newRevId = rev();
doc._revisions = {
start: newRevNum,
ids: [newRevId, oldRevId]
};
doc._rev = newRevNum + '-' + newRevId;
opts.new_edits = false;
}
}).bind(this);
doc._revisions = {
start: newRevNum,
ids: [newRevId, oldRevId]
};
doc._rev = newRevNum + '-' + newRevId;
opts.new_edits = false;
}
function putDoc(next) {
if (typeof self._put === 'function' && opts.new_edits !== false) {
self._put(doc, opts, next);
} else {
self.bulkDocs({docs: [doc]}, opts, yankError(next, doc._id));
}
}
});
this.putAttachment = adapterFun('putAttachment', function (docId, attachmentId, rev$$1, blob, type) {
var api = this;
if (typeof type === 'function') {
type = blob;
blob = rev$$1;
rev$$1 = null;
}
// Lets fix in https://github.com/pouchdb/pouchdb/issues/3267
/* istanbul ignore if */
if (typeof type === 'undefined') {
type = blob;
blob = rev$$1;
rev$$1 = null;
}
if (!type) {
guardedConsole('warn', 'Attachment', attachmentId, 'on document', docId, 'is missing content_type');
}
AbstractPouchDB.prototype.putAttachment =
adapterFun('putAttachment', function (docId, attachmentId, rev$$1,
blob, type) {
var api = this;
if (typeof type === 'function') {
type = blob;
blob = rev$$1;
rev$$1 = null;
}
// Lets fix in https://github.com/pouchdb/pouchdb/issues/3267
/* istanbul ignore if */
if (typeof type === 'undefined') {
type = blob;
blob = rev$$1;
rev$$1 = null;
}
if (!type) {
guardedConsole('warn', 'Attachment', attachmentId, 'on document', docId, 'is missing content_type');
}
function createAttachment(doc) {
var prevrevpos = '_rev' in doc ? parseInt(doc._rev, 10) : 0;
doc._attachments = doc._attachments || {};
doc._attachments[attachmentId] = {
content_type: type,
data: blob,
revpos: ++prevrevpos
};
return api.put(doc);
}
function createAttachment(doc) {
var prevrevpos = '_rev' in doc ? parseInt(doc._rev, 10) : 0;
doc._attachments = doc._attachments || {};
doc._attachments[attachmentId] = {
content_type: type,
data: blob,
revpos: ++prevrevpos
};
return api.put(doc);
}
return api.get(docId).then(function (doc) {
if (doc._rev !== rev$$1) {
throw createError(REV_CONFLICT);
}
return api.get(docId).then(function (doc) {
if (doc._rev !== rev$$1) {
throw createError(REV_CONFLICT);
}
return createAttachment(doc);
}, function (err) {
// create new doc
/* istanbul ignore else */
if (err.reason === MISSING_DOC.message) {
return createAttachment({_id: docId});
} else {
throw err;
}
});
}).bind(this);
return createAttachment(doc);
}, function (err) {
// create new doc
/* istanbul ignore else */
if (err.reason === MISSING_DOC.message) {
return createAttachment({_id: docId});
} else {
throw err;
}
});
});
this.removeAttachment = adapterFun('removeAttachment', function (docId, attachmentId, rev$$1, callback) {
this.get(docId, (err, obj) => {
/* istanbul ignore if */
if (err) {
callback(err);
return;
}
if (obj._rev !== rev$$1) {
callback(createError(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;
}
this.put(obj, callback);
});
}).bind(this);
AbstractPouchDB.prototype.removeAttachment =
adapterFun('removeAttachment', function (docId, attachmentId, rev$$1,
callback) {
var self = this;
self.get(docId, function (err, obj) {
/* istanbul ignore if */
if (err) {
callback(err);
return;
}
if (obj._rev !== rev$$1) {
callback(createError(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);
});
});
this.remove = adapterFun('remove', function (docOrId, optsOrRev, opts, callback) {
var doc;
if (typeof optsOrRev === 'string') {
// id, rev, opts, callback style
doc = {
_id: docOrId,
_rev: optsOrRev
};
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
} else {
// doc, opts, callback style
doc = docOrId;
if (typeof optsOrRev === 'function') {
callback = optsOrRev;
opts = {};
} else {
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, newDoc._id));
}).bind(this);
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
};
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
} else {
// doc, opts, callback style
doc = docOrId;
if (typeof optsOrRev === 'function') {
callback = optsOrRev;
opts = {};
} else {
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, newDoc._id));
});
this.revsDiff = adapterFun('revsDiff', function (req, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
var ids = Object.keys(req);
AbstractPouchDB.prototype.revsDiff =
adapterFun('revsDiff', function (req, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
var ids = Object.keys(req);
if (!ids.length) {
return callback(null, {});
}
if (!ids.length) {
return callback(null, {});
}
var count = 0;
var missing = new Map();
var count = 0;
var missing = new Map();
function addToMissing(id, revId) {
if (!missing.has(id)) {
missing.set(id, {missing: []});
}
missing.get(id).missing.push(revId);
}
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$$1 = pos + '-' + revHash;
var idx = missingForId.indexOf(rev$$1);
if (idx === -1) {
return;
}
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$$1 = pos + '-' + revHash;
var idx = missingForId.indexOf(rev$$1);
if (idx === -1) {
return;
}
missingForId.splice(idx, 1);
/* istanbul ignore if */
if (opts.status !== 'available') {
addToMissing(id, rev$$1);
}
});
missingForId.splice(idx, 1);
// Traversing the tree is synchronous, so now `missingForId` contains
// revisions that were not found in the tree
missingForId.forEach(function (rev$$1) {
addToMissing(id, rev$$1);
});
}
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);
}).bind(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.
this.bulkGet = adapterFun('bulkGet', function (opts, callback) {
bulkGetShim(this, opts, callback);
}).bind(this);
// 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
this.compactDocument = adapterFun('compactDocument', function (docId, maxHeight, callback) {
this._getRevisionTree(docId, (err, revTree) => {
/* istanbul ignore if */
if (opts.status !== 'available') {
addToMissing(id, rev$$1);
if (err) {
return callback(err);
}
var height = computeHeight(revTree);
var candidates = [];
var revs = [];
Object.keys(height).forEach(function (rev$$1) {
if (height[rev$$1] > maxHeight) {
candidates.push(rev$$1);
}
});
traverseRevTree(revTree, function (isLeaf, pos, revHash, ctx, opts) {
var rev$$1 = pos + '-' + revHash;
if (opts.status === 'available' && candidates.indexOf(rev$$1) !== -1) {
revs.push(rev$$1);
}
});
this._doCompaction(docId, revs, callback);
});
}).bind(this);
// Traversing the tree is synchronous, so now `missingForId` contains
// revisions that were not found in the tree
missingForId.forEach(function (rev$$1) {
addToMissing(id, rev$$1);
});
}
// compact the whole database using single document
// compaction
this.compact = adapterFun('compact', function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
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);
opts = opts || {};
this._compactionQueue = this._compactionQueue || [];
this._compactionQueue.push({opts: opts, callback: callback});
if (this._compactionQueue.length === 1) {
doNextCompaction(this);
}
}).bind(this);
if (++count === ids.length) {
// convert LazyMap to object
var missingObj = {};
missing.forEach(function (value, key) {
missingObj[key] = value;
});
return callback(null, missingObj);
/* Begin api wrappers. Specific functionality to storage belongs in the _[method] */
this.get = adapterFun('get', function (id, opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
});
}, this);
});
if (typeof id !== 'string') {
return cb(createError(INVALID_ID));
}
if (isLocalId(id) && typeof this._getLocal === 'function') {
return this._getLocal(id, cb);
}
var leaves = [];
// _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);
});
const finishOpenRevs = () => {
var result = [];
var count = leaves.length;
/* istanbul ignore if */
if (!count) {
return cb(null, result);
}
// 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
AbstractPouchDB.prototype.compactDocument =
adapterFun('compactDocument', function (docId, maxHeight, callback) {
var self = this;
this._getRevisionTree(docId, function (err, revTree) {
/* istanbul ignore if */
if (err) {
return callback(err);
}
var height = computeHeight(revTree);
var candidates = [];
var revs = [];
Object.keys(height).forEach(function (rev$$1) {
if (height[rev$$1] > maxHeight) {
candidates.push(rev$$1);
}
});
// order with open_revs is unspecified
leaves.forEach((leaf) => {
this.get(id, {
rev: leaf,
revs: opts.revs,
latest: opts.latest,
attachments: opts.attachments,
binary: opts.binary
}, function (err, doc) {
if (!err) {
// using latest=true can produce duplicates
var existing;
for (var i = 0, l = result.length; i < l; i++) {
if (result[i].ok && result[i].ok._rev === doc._rev) {
existing = true;
break;
}
}
if (!existing) {
result.push({ok: doc});
}
} else {
result.push({missing: leaf});
}
count--;
if (!count) {
cb(null, result);
}
});
});
};
traverseRevTree(revTree, function (isLeaf, pos, revHash, ctx, opts) {
var rev$$1 = pos + '-' + revHash;
if (opts.status === 'available' && candidates.indexOf(rev$$1) !== -1) {
revs.push(rev$$1);
if (opts.open_revs) {
if (opts.open_revs === "all") {
this._getRevisionTree(id, function (err, rev_tree) {
/* istanbul ignore if */
if (err) {
return cb(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 cb(createError(INVALID_REV));
}
}
finishOpenRevs();
} else {
return cb(createError(UNKNOWN_ERROR, 'function_clause'));
}
}
return; // open_revs does not like other options
}
});
self._doCompaction(docId, revs, callback);
});
});
// compact the whole database using single document
// compaction
AbstractPouchDB.prototype.compact =
adapterFun('compact', function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
return this._get(id, opts, (err, result) => {
if (err) {
err.docId = id;
return cb(err);
}
var self = this;
opts = opts || {};
var doc = result.doc;
var metadata = result.metadata;
var ctx = result.ctx;
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 = {
return_docs: false,
last_seq: opts.last_seq || 0
};
var promises = [];
if (opts.conflicts) {
var conflicts = collectConflicts(metadata);
if (conflicts.length) {
doc._conflicts = conflicts;
}
}
function onChange(row) {
promises.push(self.compactDocument(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;
if (isDeleted(metadata, doc._rev)) {
doc._deleted = true;
}
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, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
if (typeof id !== 'string') {
return cb(createError(INVALID_ID));
}
if (isLocalId(id) && typeof this._getLocal === 'function') {
return this._getLocal(id, cb);
}
var leaves = [], self = this;
if (opts.revs || opts.revs_info) {
var splittedRev = doc._rev.split('-');
var revNo = parseInt(splittedRev[0], 10);
var revHash = splittedRev[1];
function finishOpenRevs() {
var result = [];
var count = leaves.length;
/* istanbul ignore if */
if (!count) {
return cb(null, result);
}
var paths = rootToLeaf(metadata.rev_tree);
var path = null;
// order with open_revs is unspecified
leaves.forEach(function (leaf) {
self.get(id, {
rev: leaf,
revs: opts.revs,
latest: opts.latest,
attachments: opts.attachments,
binary: opts.binary
}, function (err, doc) {
if (!err) {
// using latest=true can produce duplicates
var existing;
for (var i = 0, l = result.length; i < l; i++) {
if (result[i].ok && result[i].ok._rev === doc._rev) {
existing = true;
break;
for (var i = 0; i < paths.length; i++) {
var currentPath = paths[i];
var hashIndex = currentPath.ids.map(function (x) { return x.id; })
.indexOf(revHash);
var hashFoundAtRevPos = hashIndex === (revNo - 1);
if (hashFoundAtRevPos || (!path && hashIndex !== -1)) {
path = currentPath;
}
}
if (!existing) {
result.push({ok: doc});
/* istanbul ignore if */
if (!path) {
err = new Error('invalid rev tree');
err.docId = id;
return cb(err);
}
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$$1) {
return rev$$1.id;
})
};
}
if (opts.revs_info) {
var pos = path.pos + path.ids.length;
doc._revs_info = path.ids.map(function (rev$$1) {
pos--;
return {
rev: pos + '-' + rev$$1.id,
status: rev$$1.opts.status
};
});
}
}
if (opts.attachments && doc._attachments) {
var attachments = doc._attachments;
var count = Object.keys(attachments).length;
if (count === 0) {
return cb(null, doc);
}
Object.keys(attachments).forEach((key) => {
this._getAttachment(doc._id, key, attachments[key], {
// Previously the revision handling was done in adapter.js
// getAttachment, however since idb-next doesnt we need to
// pass the rev through
rev: doc._rev,
binary: opts.binary,
ctx: ctx
}, function (err, data) {
var att = doc._attachments[key];
att.data = data;
delete att.stub;
delete att.length;
if (!--count) {
cb(null, doc);
}
});
});
} else {
result.push({missing: leaf});
if (doc._attachments) {
for (var key in doc._attachments) {
/* istanbul ignore else */
if (Object.prototype.hasOwnProperty.call(doc._attachments, key)) {
doc._attachments[key].stub = true;
}
}
}
cb(null, doc);
}
count--;
if (!count) {
cb(null, result);
}
});
});
}
}).bind(this);
if (opts.open_revs) {
if (opts.open_revs === "all") {
this._getRevisionTree(id, function (err, rev_tree) {
/* istanbul ignore if */
// TODO: I dont like this, it forces an extra read for every
// attachment read and enforces a confusing api between
// adapter.js and the adapter implementation
this.getAttachment = adapterFun('getAttachment', function (docId, attachmentId, opts, callback) {
if (opts instanceof Function) {
callback = opts;
opts = {};
}
this._get(docId, opts, (err, res) => {
if (err) {
return cb(err);
return callback(err);
}
leaves = collectLeaves(rev_tree).map(function (leaf) {
return leaf.rev;
});
finishOpenRevs();
if (res.doc._attachments && res.doc._attachments[attachmentId]) {
opts.ctx = res.ctx;
opts.binary = true;
this._getAttachment(docId, attachmentId,
res.doc._attachments[attachmentId], opts, callback);
} else {
return callback(createError(MISSING_DOC));
}
});
} 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 cb(createError(INVALID_REV));
}).bind(this);
this.allDocs = adapterFun('allDocs', function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
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(createError(QUERY_PARSE_ERROR,
'Query parameter `' + incompatibleOpt +
'` is not compatible with multi-get'
));
return;
}
if (!isRemote(this)) {
allDocsKeysParse(opts);
if (opts.keys.length === 0) {
return this._allDocs({limit: 0}, callback);
}
}
finishOpenRevs();
} else {
return cb(createError(UNKNOWN_ERROR, 'function_clause'));
}
}
return; // open_revs does not like other options
}
return this._get(id, opts, function (err, result) {
if (err) {
err.docId = id;
return cb(err);
}
return this._allDocs(opts, callback);
}).bind(this);
var doc = result.doc;
var metadata = result.metadata;
var ctx = result.ctx;
this.close = adapterFun('close', function (callback) {
this._closed = true;
this.emit('closed');
return this._close(callback);
}).bind(this);
if (opts.conflicts) {
var conflicts = collectConflicts(metadata);
if (conflicts.length) {
doc._conflicts = conflicts;
}
}
this.info = adapterFun('info', function (callback) {
this._info((err, info) => {
if (err) {
return callback(err);
}
// assume we know better than the adapter, unless it informs us
info.db_name = info.db_name || this.name;
info.auto_compaction = !!(this.auto_compaction && !isRemote(this));
info.adapter = this.adapter;
callback(null, info);
});
}).bind(this);
if (isDeleted(metadata, doc._rev)) {
doc._deleted = true;
}
this.id = adapterFun('id', function (callback) {
return this._id(callback);
}).bind(this);
if (opts.revs || opts.revs_info) {
var splittedRev = doc._rev.split('-');
var revNo = parseInt(splittedRev[0], 10);
var revHash = splittedRev[1];
this.bulkDocs = adapterFun('bulkDocs', function (req, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
var paths = rootToLeaf(metadata.rev_tree);
var path = null;
opts = opts || {};
for (var i = 0; i < paths.length; i++) {
var currentPath = paths[i];
var hashIndex = currentPath.ids.map(function (x) { return x.id; })
.indexOf(revHash);
var hashFoundAtRevPos = hashIndex === (revNo - 1);
if (Array.isArray(req)) {
req = {
docs: req
};
}
if (hashFoundAtRevPos || (!path && hashIndex !== -1)) {
path = currentPath;
}
if (!req || !req.docs || !Array.isArray(req.docs)) {
return callback(createError(MISSING_BULK_DOCS));
}
/* istanbul ignore if */
if (!path) {
err = new Error('invalid rev tree');
err.docId = id;
return cb(err);
for (var i = 0; i < req.docs.length; ++i) {
if (typeof req.docs[i] !== 'object' || Array.isArray(req.docs[i])) {
return callback(createError(NOT_AN_OBJECT));
}
}
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();
var attachmentError;
req.docs.forEach(function (doc) {
if (doc._attachments) {
Object.keys(doc._attachments).forEach(function (name) {
attachmentError = attachmentError || attachmentNameError(name);
if (!doc._attachments[name].content_type) {
guardedConsole('warn', 'Attachment', name, 'on document', doc._id, 'is missing content_type');
}
});
}
});
if (opts.revs) {
doc._revisions = {
start: (path.pos + path.ids.length) - 1,
ids: path.ids.map(function (rev$$1) {
return rev$$1.id;
})
};
if (attachmentError) {
return callback(createError(BAD_REQUEST, attachmentError));
}
if (opts.revs_info) {
var pos = path.pos + path.ids.length;
doc._revs_info = path.ids.map(function (rev$$1) {
pos--;
return {
rev: pos + '-' + rev$$1.id,
status: rev$$1.opts.status
};
});
}
}
if (opts.attachments && doc._attachments) {
var attachments = doc._attachments;
var count = Object.keys(attachments).length;
if (count === 0) {
return cb(null, doc);
}
Object.keys(attachments).forEach(function (key) {
this._getAttachment(doc._id, key, attachments[key], {
// Previously the revision handling was done in adapter.js
// getAttachment, however since idb-next doesnt we need to
// pass the rev through
rev: doc._rev,
binary: opts.binary,
ctx: ctx
}, function (err, data) {
var att = doc._attachments[key];
att.data = data;
delete att.stub;
delete att.length;
if (!--count) {
cb(null, doc);
}
});
}, self);
} else {
if (doc._attachments) {
for (var key in doc._attachments) {
/* istanbul ignore else */
if (Object.prototype.hasOwnProperty.call(doc._attachments, key)) {
doc._attachments[key].stub = true;
}
if (!('new_edits' in opts)) {
if ('new_edits' in req) {
opts.new_edits = req.new_edits;
} else {
opts.new_edits = true;
}
}
cb(null, doc);
}
});
});
// TODO: I dont like this, it forces an extra read for every
// attachment read and enforces a confusing api between
// adapter.js and the adapter implementation
AbstractPouchDB.prototype.getAttachment =
adapterFun('getAttachment', function (docId, attachmentId, opts, callback) {
var self = this;
if (opts instanceof Function) {
callback = opts;
opts = {};
}
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(docId, attachmentId,
res.doc._attachments[attachmentId], opts, callback);
} else {
return callback(createError(MISSING_DOC));
}
});
});
AbstractPouchDB.prototype.allDocs =
adapterFun('allDocs', function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
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(createError(QUERY_PARSE_ERROR,
'Query parameter `' + incompatibleOpt +
'` is not compatible with multi-get'
));
return;
}
if (!isRemote(this)) {
allDocsKeysParse(opts);
if (opts.keys.length === 0) {
return this._allDocs({limit: 0}, callback);
var adapter = this;
if (!opts.new_edits && !isRemote(adapter)) {
// ensure revisions of the same doc are sorted, so that
// the local adapter processes them correctly (#2935)
req.docs.sort(compareByIdThenRev);
}
}
}
return this._allDocs(opts, callback);
});
cleanDocs(req.docs);
AbstractPouchDB.prototype.changes = function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
// in the case of conflicts, we want to return the _ids to the user
// however, the underlying adapter may destroy the docs array, so
// create a copy here
var ids = req.docs.map(function (doc) {
return doc._id;
});
opts = opts || {};
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;
});
}
// add ids for error/conflict responses (not required for CouchDB)
if (!isRemote(adapter)) {
for (var i = 0, l = res.length; i < l; i++) {
res[i].id = res[i].id || ids[i];
}
}
// By default set return_docs to false if the caller has opts.live = true,
// this will prevent us from collecting the set of changes indefinitely
// resulting in growing memory
opts.return_docs = ('return_docs' in opts) ? opts.return_docs : !opts.live;
callback(null, res);
});
}).bind(this);
return new Changes(this, opts, callback);
};
this.registerDependentDatabase = adapterFun('registerDependentDatabase', function (dependentDb, callback) {
var dbOptions = clone(this.__opts);
if (this.__opts.view_adapter) {
dbOptions.adapter = this.__opts.view_adapter;
}
AbstractPouchDB.prototype.close = adapterFun('close', function (callback) {
this._closed = true;
this.emit('closed');
return this._close(callback);
});
var depDB = new this.constructor(dependentDb, dbOptions);
AbstractPouchDB.prototype.info = adapterFun('info', function (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.name;
info.auto_compaction = !!(self.auto_compaction && !isRemote(self));
info.adapter = self.adapter;
callback(null, info);
});
});
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);
}).bind(this);
AbstractPouchDB.prototype.id = adapterFun('id', function (callback) {
return this._id(callback);
});
this.destroy = adapterFun('destroy', function (opts, callback) {
/* istanbul ignore next */
AbstractPouchDB.prototype.type = function () {
return (typeof this._type === 'function') ? this._type() : this.adapter;
};
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
AbstractPouchDB.prototype.bulkDocs =
adapterFun('bulkDocs', function (req, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
var usePrefix = 'use_prefix' in this ? this.use_prefix : true;
opts = opts || {};
const destroyDb = () => {
// call destroy method of the particular adaptor
this._destroy(opts, (err, resp) => {
if (err) {
return callback(err);
}
this._destroyed = true;
this.emit('destroyed');
callback(null, resp || { 'ok': true });
});
};
if (Array.isArray(req)) {
req = {
docs: req
};
}
if (isRemote(this)) {
// no need to check for dependent DBs if it's a remote DB
return destroyDb();
}
if (!req || !req.docs || !Array.isArray(req.docs)) {
return callback(createError(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(createError(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 (!doc._attachments[name].content_type) {
guardedConsole('warn', 'Attachment', name, 'on document', doc._id, 'is missing content_type');
this.get('_local/_pouch_dependentDbs', (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 = this.constructor;
var deletedMap = Object.keys(dependentDbs).map((name) => {
// use_prefix is only false in the browser
/* istanbul ignore next */
var trueName = usePrefix ?
name.replace(new RegExp('^' + PouchDB.prefix), '') : name;
return new PouchDB(trueName, this.__opts).destroy();
});
Promise.all(deletedMap).then(destroyDb, callback);
});
}
});
if (attachmentError) {
return callback(createError(BAD_REQUEST, attachmentError));
}).bind(this);
}
if (!('new_edits' in opts)) {
if ('new_edits' in req) {
opts.new_edits = req.new_edits;
} else {
opts.new_edits = true;
}
}
_compact(opts, callback) {
var changesOpts = {
return_docs: false,
last_seq: opts.last_seq || 0
};
var promises = [];
var adapter = this;
if (!opts.new_edits && !isRemote(adapter)) {
// ensure revisions of the same doc are sorted, so that
// the local adapter processes them correctly (#2935)
req.docs.sort(compareByIdThenRev);
}
var taskId;
var compactedDocs = 0;
cleanDocs(req.docs);
const onChange = (row) => {
this.activeTasks.update(taskId, {
completed_items: ++compactedDocs
});
promises.push(this.compactDocument(row.id, 0));
};
const onError = (err) => {
this.activeTasks.remove(taskId, err);
callback(err);
};
const onComplete = (resp) => {
var lastSeq = resp.last_seq;
Promise.all(promises).then(() => {
return upsert(this, '_local/compaction', (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(() => {
this.activeTasks.remove(taskId);
callback(null, {ok: true});
}).catch(onError);
};
// in the case of conflicts, we want to return the _ids to the user
// however, the underlying adapter may destroy the docs array, so
// create a copy here
var ids = req.docs.map(function (doc) {
return doc._id;
});
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;
this.info().then((info) => {
taskId = this.activeTasks.add({
name: 'database_compaction',
total_items: info.update_seq - changesOpts.last_seq,
});
}
// add ids for error/conflict responses (not required for CouchDB)
if (!isRemote(adapter)) {
for (var i = 0, l = res.length; i < l; i++) {
res[i].id = res[i].id || ids[i];
}
}
callback(null, res);
});
});
AbstractPouchDB.prototype.registerDependentDatabase =
adapterFun('registerDependentDatabase', function (dependentDb,
callback) {
var dbOptions = clone(this.__opts);
if (this.__opts.view_adapter) {
dbOptions.adapter = this.__opts.view_adapter;
this.changes(changesOpts)
.on('change', onChange)
.on('complete', onComplete)
.on('error', onError);
});
}
var depDB = new this.constructor(dependentDb, dbOptions);
function diffFun(doc) {
doc.dependentDbs = doc.dependentDbs || {};
if (doc.dependentDbs[dependentDb]) {
return false; // no update required
changes(opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
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) {
opts = opts || {};
if (typeof opts === 'function') {
callback = opts;
opts = {};
// By default set return_docs to false if the caller has opts.live = true,
// this will prevent us from collecting the set of changes indefinitely
// resulting in growing memory
opts.return_docs = ('return_docs' in opts) ? opts.return_docs : !opts.live;
return new Changes(this, 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._destroyed = true;
self.emit('destroyed');
callback(null, resp || { 'ok': true });
});
type() {
return (typeof this._type === 'function') ? this._type() : this.adapter;
}
}
if (isRemote(self)) {
// no need to check for dependent DBs if it's a remote DB
return destroyDb();
// The abstract purge implementation expects a doc id and the rev of a leaf node in that doc.
// It will return errors if the rev doesn’t exist or isn’t a leaf.
AbstractPouchDB.prototype.purge = adapterFun('_purge', function (docId, rev$$1, callback) {
if (typeof this._purge === 'undefined') {
return callback(createError(UNKNOWN_ERROR, 'Purge is not implemented in the ' + this.adapter + ' adapter.'));
}
var self = this;
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();
self._getRevisionTree(docId, (error, revs) => {
if (error) {
return callback(error);
}
if (!revs) {
return callback(createError(MISSING_DOC));
}
let path;
try {
path = findPathToLeaf(revs, rev$$1);
} catch (error) {
return callback(error.message || error);
}
self._purge(docId, path, (error, result) => {
if (error) {
return callback(error);
} else {
appendPurgeSeq(self, docId, rev$$1).then(function () {
return callback(null, result);
});
}
}
var dependentDbs = localDoc.dependentDbs;
var PouchDB = self.constructor;
var deletedMap = Object.keys(dependentDbs).map(function (name) {
// use_prefix is only false in the browser
/* istanbul ignore next */
var trueName = usePrefix ?
name.replace(new RegExp('^' + PouchDB.prefix), '') : name;
return new PouchDB(trueName, self.__opts).destroy();
});
Promise.all(deletedMap).then(destroyDb, callback);
});
});
function TaskQueue() {
this.isReady = false;
this.failed = false;
this.queue = [];
}
class TaskQueue {
constructor() {
this.isReady = false;
this.failed = false;
this.queue = [];
}
TaskQueue.prototype.execute = function () {
var fun;
if (this.failed) {
while ((fun = this.queue.shift())) {
fun(this.failed);
execute() {
var fun;
if (this.failed) {
while ((fun = this.queue.shift())) {
fun(this.failed);
}
} else {
while ((fun = this.queue.shift())) {
fun();
}
}
} else {
while ((fun = this.queue.shift())) {
fun();
}
}
};
TaskQueue.prototype.fail = function (err) {
this.failed = err;
this.execute();
};
fail(err) {
this.failed = err;
this.execute();
}
TaskQueue.prototype.ready = function (db) {
this.isReady = true;
this.db = db;
this.execute();
};
TaskQueue.prototype.addTask = function (fun) {
this.queue.push(fun);
if (this.failed) {
ready(db) {
this.isReady = true;
this.db = db;
this.execute();
}
};
addTask(fun) {
this.queue.push(fun);
if (this.failed) {
this.execute();
}
}
}
function parseAdapter(name, opts) {

@@ -1226,2 +1276,19 @@ var match = name.match(/([a-z-]*):\/\/(.*)/);

function inherits(A, B) {
A.prototype = Object.create(B.prototype, {
constructor: { value: A }
});
}
function createClass(parent, init) {
let klass = function (...args) {
if (!(this instanceof klass)) {
return new klass(...args);
}
init.apply(this, args);
};
inherits(klass, parent);
return klass;
}
// OK, so here's the deal. Consider this code:

@@ -1256,70 +1323,120 @@ // var db1 = new PouchDB('foo');

inherits(PouchDB, AbstractPouchDB);
function PouchDB(name, opts) {
// In Node our test suite only tests this for PouchAlt unfortunately
/* istanbul ignore if */
if (!(this instanceof PouchDB)) {
return new PouchDB(name, opts);
class PouchInternal extends AbstractPouchDB {
constructor(name, opts) {
super();
this._setup(name, opts);
}
var self = this;
opts = opts || {};
_setup(name, opts) {
super._setup();
opts = opts || {};
if (name && typeof name === 'object') {
opts = name;
name = opts.name;
delete opts.name;
}
if (name && typeof name === 'object') {
opts = name;
name = opts.name;
delete opts.name;
}
if (opts.deterministic_revs === undefined) {
opts.deterministic_revs = true;
}
if (opts.deterministic_revs === undefined) {
opts.deterministic_revs = true;
}
this.__opts = opts = clone(opts);
this.__opts = opts = clone(opts);
self.auto_compaction = opts.auto_compaction;
self.prefix = PouchDB.prefix;
this.auto_compaction = opts.auto_compaction;
this.purged_infos_limit = opts.purged_infos_limit || 1000;
this.prefix = PouchDB.prefix;
if (typeof name !== 'string') {
throw new Error('Missing/invalid DB name');
}
if (typeof name !== 'string') {
throw new Error('Missing/invalid DB name');
}
var prefixedName = (opts.prefix || '') + name;
var backend = parseAdapter(prefixedName, opts);
var prefixedName = (opts.prefix || '') + name;
var backend = parseAdapter(prefixedName, opts);
opts.name = backend.name;
opts.adapter = opts.adapter || backend.adapter;
opts.name = backend.name;
opts.adapter = opts.adapter || backend.adapter;
self.name = name;
self._adapter = opts.adapter;
PouchDB.emit('debug', ['adapter', 'Picked adapter: ', opts.adapter]);
this.name = name;
this._adapter = opts.adapter;
PouchDB.emit('debug', ['adapter', 'Picked adapter: ', opts.adapter]);
if (!PouchDB.adapters[opts.adapter] ||
!PouchDB.adapters[opts.adapter].valid()) {
throw new Error('Invalid Adapter: ' + opts.adapter);
}
if (!PouchDB.adapters[opts.adapter] ||
!PouchDB.adapters[opts.adapter].valid()) {
throw new Error('Invalid Adapter: ' + opts.adapter);
}
if (opts.view_adapter) {
if (!PouchDB.adapters[opts.view_adapter] ||
!PouchDB.adapters[opts.view_adapter].valid()) {
throw new Error('Invalid View Adapter: ' + opts.view_adapter);
if (opts.view_adapter) {
if (!PouchDB.adapters[opts.view_adapter] ||
!PouchDB.adapters[opts.view_adapter].valid()) {
throw new Error('Invalid View Adapter: ' + opts.view_adapter);
}
}
this.taskqueue = new TaskQueue();
this.adapter = opts.adapter;
PouchDB.adapters[opts.adapter].call(this, opts, (err) => {
if (err) {
return this.taskqueue.fail(err);
}
prepareForDestruction(this);
this.emit('created', this);
PouchDB.emit('created', this.name);
this.taskqueue.ready(this);
});
}
}
AbstractPouchDB.call(self);
self.taskqueue = new TaskQueue();
const PouchDB = createClass(PouchInternal, function (name, opts) {
PouchInternal.prototype._setup.call(this, name, opts);
});
self.adapter = opts.adapter;
class ActiveTasks {
constructor() {
this.tasks = {};
}
PouchDB.adapters[opts.adapter].call(self, opts, function (err) {
if (err) {
return self.taskqueue.fail(err);
}
prepareForDestruction(self);
list() {
return Object.values(this.tasks);
}
self.emit('created', self);
PouchDB.emit('created', self.name);
self.taskqueue.ready(self);
});
add(task) {
const id = v4();
this.tasks[id] = {
id,
name: task.name,
total_items: task.total_items,
created_at: new Date().toJSON()
};
return id;
}
get(id) {
return this.tasks[id];
}
/* eslint-disable no-unused-vars */
remove(id, reason) {
delete this.tasks[id];
return this.tasks;
}
update(id, updatedTask) {
const task = this.tasks[id];
if (typeof task !== 'undefined') {
const mergedTask = {
id: task.id,
name: task.name,
created_at: task.created_at,
total_items: updatedTask.total_items || task.total_items,
completed_items: updatedTask.completed_items || task.completed_items,
updated_at: new Date().toJSON()
};
this.tasks[id] = mergedTask;
}
return this.tasks;
}
}

@@ -1412,7 +1529,3 @@

PouchDB.defaults = function (defaultOpts) {
function PouchAlt(name, opts) {
if (!(this instanceof PouchAlt)) {
return new PouchAlt(name, opts);
}
let PouchWithDefaults = createClass(PouchDB, function (name, opts) {
opts = opts || {};

@@ -1426,12 +1539,10 @@

opts = assign({}, PouchAlt.__defaults, opts);
opts = assign({}, PouchWithDefaults.__defaults, opts);
PouchDB.call(this, name, opts);
}
});
inherits(PouchAlt, PouchDB);
PouchAlt.preferredAdapters = PouchDB.preferredAdapters.slice();
PouchWithDefaults.preferredAdapters = PouchDB.preferredAdapters.slice();
Object.keys(PouchDB).forEach(function (key) {
if (!(key in PouchAlt)) {
PouchAlt[key] = PouchDB[key];
if (!(key in PouchWithDefaults)) {
PouchWithDefaults[key] = PouchDB[key];
}

@@ -1442,5 +1553,5 @@ });

// https://github.com/pouchdb/pouchdb/issues/5922
PouchAlt.__defaults = assign({}, this.__defaults, defaultOpts);
PouchWithDefaults.__defaults = assign({}, this.__defaults, defaultOpts);
return PouchAlt;
return PouchWithDefaults;
};

@@ -1452,4 +1563,6 @@

PouchDB.prototype.activeTasks = PouchDB.activeTasks = new ActiveTasks();
// managed automatically by set-version.js
var version = "7.3.1";
var version = "8.0.0";

@@ -1456,0 +1569,0 @@ // TODO: remove from pouchdb-core (breaking)

@@ -6,13 +6,10 @@ 'use strict';

var pouchdbCollections = require('pouchdb-collections');
var getArguments = _interopDefault(require('argsarray'));
var pouchdbMerge = require('pouchdb-merge');
var pouchdbErrors = require('pouchdb-errors');
var pouchdbUtils = require('pouchdb-utils');
var inherits = _interopDefault(require('inherits'));
var EE = _interopDefault(require('events'));
var pouchdbFetch = require('pouchdb-fetch');
var uuid = require('uuid');
var pouchChangesFilter = _interopDefault(require('pouchdb-changes-filter'));
inherits(Changes, EE);
function tryCatchInChangeListener(self, change, pending, lastSeq) {

@@ -27,78 +24,2 @@ // isolate try/catches to avoid V8 deoptimizations

function Changes(db, opts, callback) {
EE.call(this);
var self = this;
this.db = db;
opts = opts ? pouchdbUtils.clone(opts) : {};
var complete = opts.complete = pouchdbUtils.once(function (err, resp) {
if (err) {
if (pouchdbUtils.listenerCount(self, 'error') > 0) {
self.emit('error', err);
}
} else {
self.emit('complete', resp);
}
self.removeAllListeners();
db.removeListener('destroyed', onDestroy);
});
if (callback) {
self.on('complete', function (resp) {
callback(null, resp);
});
self.on('error', callback);
}
function onDestroy() {
self.cancel();
}
db.once('destroyed', onDestroy);
opts.onChange = function (change, pending, lastSeq) {
/* istanbul ignore if */
if (self.isCancelled) {
return;
}
tryCatchInChangeListener(self, change, pending, lastSeq);
};
var promise = new Promise(function (fulfill, reject) {
opts.complete = function (err, res) {
if (err) {
reject(err);
} else {
fulfill(res);
}
};
});
self.once('cancel', function () {
db.removeListener('destroyed', onDestroy);
opts.complete(null, {status: 'cancelled'});
});
this.then = promise.then.bind(promise);
this['catch'] = promise['catch'].bind(promise);
this.then(function (result) {
complete(null, result);
}, complete);
if (!db.taskqueue.isReady) {
db.taskqueue.addTask(function (failed) {
if (failed) {
opts.complete(failed);
} else if (self.isCancelled) {
self.emit('cancel');
} else {
self.validateChanges(opts);
}
});
} else {
self.validateChanges(opts);
}
}
Changes.prototype.cancel = function () {
this.isCancelled = true;
if (this.db.taskqueue.isReady) {
this.emit('cancel');
}
};
function processChange(doc, metadata, opts) {

@@ -128,83 +49,160 @@ var changeList = [{rev: doc._rev}];

Changes.prototype.validateChanges = function (opts) {
var callback = opts.complete;
var self = this;
/* istanbul ignore else */
if (PouchDB._changesFilterPlugin) {
PouchDB._changesFilterPlugin.validate(opts, function (err) {
class Changes extends EE {
constructor(db, opts, callback) {
super();
this.db = db;
opts = opts ? pouchdbUtils.clone(opts) : {};
var complete = opts.complete = pouchdbUtils.once((err, resp) => {
if (err) {
return callback(err);
if (pouchdbUtils.listenerCount(this, 'error') > 0) {
this.emit('error', err);
}
} else {
this.emit('complete', resp);
}
self.doChanges(opts);
this.removeAllListeners();
db.removeListener('destroyed', onDestroy);
});
} else {
self.doChanges(opts);
}
};
Changes.prototype.doChanges = function (opts) {
var self = this;
var callback = opts.complete;
opts = pouchdbUtils.clone(opts);
if ('live' in opts && !('continuous' in opts)) {
opts.continuous = opts.live;
}
opts.processChange = processChange;
if (opts.since === 'latest') {
opts.since = 'now';
}
if (!opts.since) {
opts.since = 0;
}
if (opts.since === 'now') {
this.db.info().then(function (info) {
if (callback) {
this.on('complete', function (resp) {
callback(null, resp);
});
this.on('error', callback);
}
const onDestroy = () => {
this.cancel();
};
db.once('destroyed', onDestroy);
opts.onChange = (change, pending, lastSeq) => {
/* istanbul ignore if */
if (self.isCancelled) {
callback(null, {status: 'cancelled'});
if (this.isCancelled) {
return;
}
opts.since = info.update_seq;
self.doChanges(opts);
}, callback);
return;
tryCatchInChangeListener(this, change, pending, lastSeq);
};
var promise = new Promise(function (fulfill, reject) {
opts.complete = function (err, res) {
if (err) {
reject(err);
} else {
fulfill(res);
}
};
});
this.once('cancel', function () {
db.removeListener('destroyed', onDestroy);
opts.complete(null, {status: 'cancelled'});
});
this.then = promise.then.bind(promise);
this['catch'] = promise['catch'].bind(promise);
this.then(function (result) {
complete(null, result);
}, complete);
if (!db.taskqueue.isReady) {
db.taskqueue.addTask((failed) => {
if (failed) {
opts.complete(failed);
} else if (this.isCancelled) {
this.emit('cancel');
} else {
this.validateChanges(opts);
}
});
} else {
this.validateChanges(opts);
}
}
/* istanbul ignore else */
if (PouchDB._changesFilterPlugin) {
PouchDB._changesFilterPlugin.normalize(opts);
if (PouchDB._changesFilterPlugin.shouldFilter(this, opts)) {
return PouchDB._changesFilterPlugin.filter(this, opts);
cancel() {
this.isCancelled = true;
if (this.db.taskqueue.isReady) {
this.emit('cancel');
}
} else {
['doc_ids', 'filter', 'selector', 'view'].forEach(function (key) {
if (key in opts) {
pouchdbUtils.guardedConsole('warn',
'The "' + key + '" option was passed in to changes/replicate, ' +
'but pouchdb-changes-filter plugin is not installed, so it ' +
'was ignored. Please install the plugin to enable filtering.'
);
}
});
}
if (!('descending' in opts)) {
opts.descending = false;
validateChanges(opts) {
var callback = opts.complete;
/* istanbul ignore else */
if (PouchDB._changesFilterPlugin) {
PouchDB._changesFilterPlugin.validate(opts, (err) => {
if (err) {
return callback(err);
}
this.doChanges(opts);
});
} else {
this.doChanges(opts);
}
}
// 0 and 1 should return 1 document
opts.limit = opts.limit === 0 ? 1 : opts.limit;
opts.complete = callback;
var newPromise = this.db._changes(opts);
/* istanbul ignore else */
if (newPromise && typeof newPromise.cancel === 'function') {
var cancel = self.cancel;
self.cancel = getArguments(function (args) {
newPromise.cancel();
cancel.apply(this, args);
});
doChanges(opts) {
var callback = opts.complete;
opts = pouchdbUtils.clone(opts);
if ('live' in opts && !('continuous' in opts)) {
opts.continuous = opts.live;
}
opts.processChange = processChange;
if (opts.since === 'latest') {
opts.since = 'now';
}
if (!opts.since) {
opts.since = 0;
}
if (opts.since === 'now') {
this.db.info().then((info) => {
/* istanbul ignore if */
if (this.isCancelled) {
callback(null, {status: 'cancelled'});
return;
}
opts.since = info.update_seq;
this.doChanges(opts);
}, callback);
return;
}
/* istanbul ignore else */
if (PouchDB._changesFilterPlugin) {
PouchDB._changesFilterPlugin.normalize(opts);
if (PouchDB._changesFilterPlugin.shouldFilter(this, opts)) {
return PouchDB._changesFilterPlugin.filter(this, opts);
}
} else {
['doc_ids', 'filter', 'selector', 'view'].forEach(function (key) {
if (key in opts) {
pouchdbUtils.guardedConsole('warn',
'The "' + key + '" option was passed in to changes/replicate, ' +
'but pouchdb-changes-filter plugin is not installed, so it ' +
'was ignored. Please install the plugin to enable filtering.'
);
}
});
}
if (!('descending' in opts)) {
opts.descending = false;
}
// 0 and 1 should return 1 document
opts.limit = opts.limit === 0 ? 1 : opts.limit;
opts.complete = callback;
var newPromise = this.db._changes(opts);
/* istanbul ignore else */
if (newPromise && typeof newPromise.cancel === 'function') {
const cancel = this.cancel;
this.cancel = (...args) => {
newPromise.cancel();
cancel.apply(this, args);
};
}
}
};
}

@@ -331,2 +329,33 @@ /*

function appendPurgeSeq(db, docId, rev) {
return db.get('_local/purges').then(function (doc) {
const purgeSeq = doc.purgeSeq + 1;
doc.purges.push({
docId,
rev,
purgeSeq,
});
if (doc.purges.length > self.purged_infos_limit) {
doc.purges.splice(0, doc.purges.length - self.purged_infos_limit);
}
doc.purgeSeq = purgeSeq;
return doc;
}).catch(function (err) {
if (err.status !== 404) {
throw err;
}
return {
_id: '_local/purges',
purges: [{
docId,
rev,
purgeSeq: 0,
}],
purgeSeq: 0,
};
}).then(function (doc) {
return db.put(doc);
});
}
function attachmentNameError(name) {

@@ -340,849 +369,870 @@ if (name.charAt(0) === '_') {

inherits(AbstractPouchDB, EE);
class AbstractPouchDB extends EE {
_setup() {
this.post = pouchdbUtils.adapterFun('post', function (doc, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
if (typeof doc !== 'object' || Array.isArray(doc)) {
return callback(pouchdbErrors.createError(pouchdbErrors.NOT_AN_OBJECT));
}
this.bulkDocs({docs: [doc]}, opts, yankError(callback, doc._id));
}).bind(this);
function AbstractPouchDB() {
EE.call(this);
this.put = pouchdbUtils.adapterFun('put', function (doc, opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
if (typeof doc !== 'object' || Array.isArray(doc)) {
return cb(pouchdbErrors.createError(pouchdbErrors.NOT_AN_OBJECT));
}
pouchdbUtils.invalidIdError(doc._id);
if (pouchdbMerge.isLocalId(doc._id) && typeof this._putLocal === 'function') {
if (doc._deleted) {
return this._removeLocal(doc, cb);
} else {
return this._putLocal(doc, cb);
}
}
// re-bind prototyped methods
for (var p in AbstractPouchDB.prototype) {
if (typeof this[p] === 'function') {
this[p] = this[p].bind(this);
}
}
}
const putDoc = (next) => {
if (typeof this._put === 'function' && opts.new_edits !== false) {
this._put(doc, opts, next);
} else {
this.bulkDocs({docs: [doc]}, opts, yankError(next, doc._id));
}
};
AbstractPouchDB.prototype.post =
pouchdbUtils.adapterFun('post', function (doc, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
if (typeof doc !== 'object' || Array.isArray(doc)) {
return callback(pouchdbErrors.createError(pouchdbErrors.NOT_AN_OBJECT));
}
this.bulkDocs({docs: [doc]}, opts, yankError(callback, doc._id));
});
if (opts.force && doc._rev) {
transformForceOptionToNewEditsOption();
putDoc(function (err) {
var result = err ? null : {ok: true, id: doc._id, rev: doc._rev};
cb(err, result);
});
} else {
putDoc(cb);
}
AbstractPouchDB.prototype.put = pouchdbUtils.adapterFun('put', function (doc, opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
if (typeof doc !== 'object' || Array.isArray(doc)) {
return cb(pouchdbErrors.createError(pouchdbErrors.NOT_AN_OBJECT));
}
pouchdbUtils.invalidIdError(doc._id);
if (pouchdbMerge.isLocalId(doc._id) && typeof this._putLocal === 'function') {
if (doc._deleted) {
return this._removeLocal(doc, cb);
} else {
return this._putLocal(doc, cb);
}
}
var self = this;
if (opts.force && doc._rev) {
transformForceOptionToNewEditsOption();
putDoc(function (err) {
var result = err ? null : {ok: true, id: doc._id, rev: doc._rev};
cb(err, result);
});
} else {
putDoc(cb);
}
function transformForceOptionToNewEditsOption() {
var parts = doc._rev.split('-');
var oldRevId = parts[1];
var oldRevNum = parseInt(parts[0], 10);
function transformForceOptionToNewEditsOption() {
var parts = doc._rev.split('-');
var oldRevId = parts[1];
var oldRevNum = parseInt(parts[0], 10);
var newRevNum = oldRevNum + 1;
var newRevId = pouchdbUtils.rev();
var newRevNum = oldRevNum + 1;
var newRevId = pouchdbUtils.rev();
doc._revisions = {
start: newRevNum,
ids: [newRevId, oldRevId]
};
doc._rev = newRevNum + '-' + newRevId;
opts.new_edits = false;
}
}).bind(this);
doc._revisions = {
start: newRevNum,
ids: [newRevId, oldRevId]
};
doc._rev = newRevNum + '-' + newRevId;
opts.new_edits = false;
}
function putDoc(next) {
if (typeof self._put === 'function' && opts.new_edits !== false) {
self._put(doc, opts, next);
} else {
self.bulkDocs({docs: [doc]}, opts, yankError(next, doc._id));
}
}
});
this.putAttachment = pouchdbUtils.adapterFun('putAttachment', function (docId, attachmentId, rev, blob, type) {
var api = this;
if (typeof type === 'function') {
type = blob;
blob = rev;
rev = null;
}
// Lets fix in https://github.com/pouchdb/pouchdb/issues/3267
/* istanbul ignore if */
if (typeof type === 'undefined') {
type = blob;
blob = rev;
rev = null;
}
if (!type) {
pouchdbUtils.guardedConsole('warn', 'Attachment', attachmentId, 'on document', docId, 'is missing content_type');
}
AbstractPouchDB.prototype.putAttachment =
pouchdbUtils.adapterFun('putAttachment', function (docId, attachmentId, rev,
blob, type) {
var api = this;
if (typeof type === 'function') {
type = blob;
blob = rev;
rev = null;
}
// Lets fix in https://github.com/pouchdb/pouchdb/issues/3267
/* istanbul ignore if */
if (typeof type === 'undefined') {
type = blob;
blob = rev;
rev = null;
}
if (!type) {
pouchdbUtils.guardedConsole('warn', 'Attachment', attachmentId, 'on document', docId, 'is missing content_type');
}
function createAttachment(doc) {
var prevrevpos = '_rev' in doc ? parseInt(doc._rev, 10) : 0;
doc._attachments = doc._attachments || {};
doc._attachments[attachmentId] = {
content_type: type,
data: blob,
revpos: ++prevrevpos
};
return api.put(doc);
}
function createAttachment(doc) {
var prevrevpos = '_rev' in doc ? parseInt(doc._rev, 10) : 0;
doc._attachments = doc._attachments || {};
doc._attachments[attachmentId] = {
content_type: type,
data: blob,
revpos: ++prevrevpos
};
return api.put(doc);
}
return api.get(docId).then(function (doc) {
if (doc._rev !== rev) {
throw pouchdbErrors.createError(pouchdbErrors.REV_CONFLICT);
}
return api.get(docId).then(function (doc) {
if (doc._rev !== rev) {
throw pouchdbErrors.createError(pouchdbErrors.REV_CONFLICT);
}
return createAttachment(doc);
}, function (err) {
// create new doc
/* istanbul ignore else */
if (err.reason === pouchdbErrors.MISSING_DOC.message) {
return createAttachment({_id: docId});
} else {
throw err;
}
});
}).bind(this);
return createAttachment(doc);
}, function (err) {
// create new doc
/* istanbul ignore else */
if (err.reason === pouchdbErrors.MISSING_DOC.message) {
return createAttachment({_id: docId});
} else {
throw err;
}
});
});
this.removeAttachment = pouchdbUtils.adapterFun('removeAttachment', function (docId, attachmentId, rev, callback) {
this.get(docId, (err, obj) => {
/* istanbul ignore if */
if (err) {
callback(err);
return;
}
if (obj._rev !== rev) {
callback(pouchdbErrors.createError(pouchdbErrors.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;
}
this.put(obj, callback);
});
}).bind(this);
AbstractPouchDB.prototype.removeAttachment =
pouchdbUtils.adapterFun('removeAttachment', function (docId, attachmentId, rev,
callback) {
var self = this;
self.get(docId, function (err, obj) {
/* istanbul ignore if */
if (err) {
callback(err);
return;
}
if (obj._rev !== rev) {
callback(pouchdbErrors.createError(pouchdbErrors.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);
});
});
this.remove = pouchdbUtils.adapterFun('remove', function (docOrId, optsOrRev, opts, callback) {
var doc;
if (typeof optsOrRev === 'string') {
// id, rev, opts, callback style
doc = {
_id: docOrId,
_rev: optsOrRev
};
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
} else {
// doc, opts, callback style
doc = docOrId;
if (typeof optsOrRev === 'function') {
callback = optsOrRev;
opts = {};
} else {
callback = opts;
opts = optsOrRev;
}
}
opts = opts || {};
opts.was_delete = true;
var newDoc = {_id: doc._id, _rev: (doc._rev || opts.rev)};
newDoc._deleted = true;
if (pouchdbMerge.isLocalId(newDoc._id) && typeof this._removeLocal === 'function') {
return this._removeLocal(doc, callback);
}
this.bulkDocs({docs: [newDoc]}, opts, yankError(callback, newDoc._id));
}).bind(this);
AbstractPouchDB.prototype.remove =
pouchdbUtils.adapterFun('remove', function (docOrId, optsOrRev, opts, callback) {
var doc;
if (typeof optsOrRev === 'string') {
// id, rev, opts, callback style
doc = {
_id: docOrId,
_rev: optsOrRev
};
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
} else {
// doc, opts, callback style
doc = docOrId;
if (typeof optsOrRev === 'function') {
callback = optsOrRev;
opts = {};
} else {
callback = opts;
opts = optsOrRev;
}
}
opts = opts || {};
opts.was_delete = true;
var newDoc = {_id: doc._id, _rev: (doc._rev || opts.rev)};
newDoc._deleted = true;
if (pouchdbMerge.isLocalId(newDoc._id) && typeof this._removeLocal === 'function') {
return this._removeLocal(doc, callback);
}
this.bulkDocs({docs: [newDoc]}, opts, yankError(callback, newDoc._id));
});
this.revsDiff = pouchdbUtils.adapterFun('revsDiff', function (req, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
var ids = Object.keys(req);
AbstractPouchDB.prototype.revsDiff =
pouchdbUtils.adapterFun('revsDiff', function (req, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
var ids = Object.keys(req);
if (!ids.length) {
return callback(null, {});
}
if (!ids.length) {
return callback(null, {});
}
var count = 0;
var missing = new pouchdbCollections.Map();
var count = 0;
var missing = new pouchdbCollections.Map();
function addToMissing(id, revId) {
if (!missing.has(id)) {
missing.set(id, {missing: []});
}
missing.get(id).missing.push(revId);
}
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);
pouchdbMerge.traverseRevTree(rev_tree, function (isLeaf, pos, revHash, ctx,
opts) {
var rev = pos + '-' + revHash;
var idx = missingForId.indexOf(rev);
if (idx === -1) {
return;
}
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);
pouchdbMerge.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);
}
});
missingForId.splice(idx, 1);
// 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);
}).bind(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.
this.bulkGet = pouchdbUtils.adapterFun('bulkGet', function (opts, callback) {
pouchdbUtils.bulkGetShim(this, opts, callback);
}).bind(this);
// 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
this.compactDocument = pouchdbUtils.adapterFun('compactDocument', function (docId, maxHeight, callback) {
this._getRevisionTree(docId, (err, revTree) => {
/* istanbul ignore if */
if (opts.status !== 'available') {
addToMissing(id, rev);
if (err) {
return callback(err);
}
var height = computeHeight(revTree);
var candidates = [];
var revs = [];
Object.keys(height).forEach(function (rev) {
if (height[rev] > maxHeight) {
candidates.push(rev);
}
});
pouchdbMerge.traverseRevTree(revTree, function (isLeaf, pos, revHash, ctx, opts) {
var rev = pos + '-' + revHash;
if (opts.status === 'available' && candidates.indexOf(rev) !== -1) {
revs.push(rev);
}
});
this._doCompaction(docId, revs, callback);
});
}).bind(this);
// Traversing the tree is synchronous, so now `missingForId` contains
// revisions that were not found in the tree
missingForId.forEach(function (rev) {
addToMissing(id, rev);
});
}
// compact the whole database using single document
// compaction
this.compact = pouchdbUtils.adapterFun('compact', function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
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);
opts = opts || {};
this._compactionQueue = this._compactionQueue || [];
this._compactionQueue.push({opts: opts, callback: callback});
if (this._compactionQueue.length === 1) {
doNextCompaction(this);
}
}).bind(this);
if (++count === ids.length) {
// convert LazyMap to object
var missingObj = {};
missing.forEach(function (value, key) {
missingObj[key] = value;
});
return callback(null, missingObj);
/* Begin api wrappers. Specific functionality to storage belongs in the _[method] */
this.get = pouchdbUtils.adapterFun('get', function (id, opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
});
}, this);
});
if (typeof id !== 'string') {
return cb(pouchdbErrors.createError(pouchdbErrors.INVALID_ID));
}
if (pouchdbMerge.isLocalId(id) && typeof this._getLocal === 'function') {
return this._getLocal(id, cb);
}
var leaves = [];
// _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 =
pouchdbUtils.adapterFun('bulkGet', function (opts, callback) {
pouchdbUtils.bulkGetShim(this, opts, callback);
});
const finishOpenRevs = () => {
var result = [];
var count = leaves.length;
/* istanbul ignore if */
if (!count) {
return cb(null, result);
}
// 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
AbstractPouchDB.prototype.compactDocument =
pouchdbUtils.adapterFun('compactDocument', function (docId, maxHeight, callback) {
var self = this;
this._getRevisionTree(docId, function (err, revTree) {
/* istanbul ignore if */
if (err) {
return callback(err);
}
var height = computeHeight(revTree);
var candidates = [];
var revs = [];
Object.keys(height).forEach(function (rev) {
if (height[rev] > maxHeight) {
candidates.push(rev);
}
});
// order with open_revs is unspecified
leaves.forEach((leaf) => {
this.get(id, {
rev: leaf,
revs: opts.revs,
latest: opts.latest,
attachments: opts.attachments,
binary: opts.binary
}, function (err, doc) {
if (!err) {
// using latest=true can produce duplicates
var existing;
for (var i = 0, l = result.length; i < l; i++) {
if (result[i].ok && result[i].ok._rev === doc._rev) {
existing = true;
break;
}
}
if (!existing) {
result.push({ok: doc});
}
} else {
result.push({missing: leaf});
}
count--;
if (!count) {
cb(null, result);
}
});
});
};
pouchdbMerge.traverseRevTree(revTree, function (isLeaf, pos, revHash, ctx, opts) {
var rev = pos + '-' + revHash;
if (opts.status === 'available' && candidates.indexOf(rev) !== -1) {
revs.push(rev);
if (opts.open_revs) {
if (opts.open_revs === "all") {
this._getRevisionTree(id, function (err, rev_tree) {
/* istanbul ignore if */
if (err) {
return cb(err);
}
leaves = pouchdbMerge.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 cb(pouchdbErrors.createError(pouchdbErrors.INVALID_REV));
}
}
finishOpenRevs();
} else {
return cb(pouchdbErrors.createError(pouchdbErrors.UNKNOWN_ERROR, 'function_clause'));
}
}
return; // open_revs does not like other options
}
});
self._doCompaction(docId, revs, callback);
});
});
// compact the whole database using single document
// compaction
AbstractPouchDB.prototype.compact =
pouchdbUtils.adapterFun('compact', function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
return this._get(id, opts, (err, result) => {
if (err) {
err.docId = id;
return cb(err);
}
var self = this;
opts = opts || {};
var doc = result.doc;
var metadata = result.metadata;
var ctx = result.ctx;
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 = {
return_docs: false,
last_seq: opts.last_seq || 0
};
var promises = [];
if (opts.conflicts) {
var conflicts = pouchdbMerge.collectConflicts(metadata);
if (conflicts.length) {
doc._conflicts = conflicts;
}
}
function onChange(row) {
promises.push(self.compactDocument(row.id, 0));
}
function onComplete(resp) {
var lastSeq = resp.last_seq;
Promise.all(promises).then(function () {
return pouchdbUtils.upsert(self, '_local/compaction', function deltaFunc(doc) {
if (!doc.last_seq || doc.last_seq < lastSeq) {
doc.last_seq = lastSeq;
return doc;
if (pouchdbMerge.isDeleted(metadata, doc._rev)) {
doc._deleted = true;
}
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 = pouchdbUtils.adapterFun('get', function (id, opts, cb) {
if (typeof opts === 'function') {
cb = opts;
opts = {};
}
if (typeof id !== 'string') {
return cb(pouchdbErrors.createError(pouchdbErrors.INVALID_ID));
}
if (pouchdbMerge.isLocalId(id) && typeof this._getLocal === 'function') {
return this._getLocal(id, cb);
}
var leaves = [], self = this;
if (opts.revs || opts.revs_info) {
var splittedRev = doc._rev.split('-');
var revNo = parseInt(splittedRev[0], 10);
var revHash = splittedRev[1];
function finishOpenRevs() {
var result = [];
var count = leaves.length;
/* istanbul ignore if */
if (!count) {
return cb(null, result);
}
var paths = pouchdbMerge.rootToLeaf(metadata.rev_tree);
var path = null;
// order with open_revs is unspecified
leaves.forEach(function (leaf) {
self.get(id, {
rev: leaf,
revs: opts.revs,
latest: opts.latest,
attachments: opts.attachments,
binary: opts.binary
}, function (err, doc) {
if (!err) {
// using latest=true can produce duplicates
var existing;
for (var i = 0, l = result.length; i < l; i++) {
if (result[i].ok && result[i].ok._rev === doc._rev) {
existing = true;
break;
for (var i = 0; i < paths.length; i++) {
var currentPath = paths[i];
var hashIndex = currentPath.ids.map(function (x) { return x.id; })
.indexOf(revHash);
var hashFoundAtRevPos = hashIndex === (revNo - 1);
if (hashFoundAtRevPos || (!path && hashIndex !== -1)) {
path = currentPath;
}
}
if (!existing) {
result.push({ok: doc});
/* istanbul ignore if */
if (!path) {
err = new Error('invalid rev tree');
err.docId = id;
return cb(err);
}
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 cb(null, doc);
}
Object.keys(attachments).forEach((key) => {
this._getAttachment(doc._id, key, attachments[key], {
// Previously the revision handling was done in adapter.js
// getAttachment, however since idb-next doesnt we need to
// pass the rev through
rev: doc._rev,
binary: opts.binary,
ctx: ctx
}, function (err, data) {
var att = doc._attachments[key];
att.data = data;
delete att.stub;
delete att.length;
if (!--count) {
cb(null, doc);
}
});
});
} else {
result.push({missing: leaf});
if (doc._attachments) {
for (var key in doc._attachments) {
/* istanbul ignore else */
if (Object.prototype.hasOwnProperty.call(doc._attachments, key)) {
doc._attachments[key].stub = true;
}
}
}
cb(null, doc);
}
count--;
if (!count) {
cb(null, result);
}
});
});
}
}).bind(this);
if (opts.open_revs) {
if (opts.open_revs === "all") {
this._getRevisionTree(id, function (err, rev_tree) {
/* istanbul ignore if */
// TODO: I dont like this, it forces an extra read for every
// attachment read and enforces a confusing api between
// adapter.js and the adapter implementation
this.getAttachment = pouchdbUtils.adapterFun('getAttachment', function (docId, attachmentId, opts, callback) {
if (opts instanceof Function) {
callback = opts;
opts = {};
}
this._get(docId, opts, (err, res) => {
if (err) {
return cb(err);
return callback(err);
}
leaves = pouchdbMerge.collectLeaves(rev_tree).map(function (leaf) {
return leaf.rev;
});
finishOpenRevs();
if (res.doc._attachments && res.doc._attachments[attachmentId]) {
opts.ctx = res.ctx;
opts.binary = true;
this._getAttachment(docId, attachmentId,
res.doc._attachments[attachmentId], opts, callback);
} else {
return callback(pouchdbErrors.createError(pouchdbErrors.MISSING_DOC));
}
});
} 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 cb(pouchdbErrors.createError(pouchdbErrors.INVALID_REV));
}).bind(this);
this.allDocs = pouchdbUtils.adapterFun('allDocs', function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
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(pouchdbErrors.createError(pouchdbErrors.QUERY_PARSE_ERROR,
'Query parameter `' + incompatibleOpt +
'` is not compatible with multi-get'
));
return;
}
if (!pouchdbUtils.isRemote(this)) {
allDocsKeysParse(opts);
if (opts.keys.length === 0) {
return this._allDocs({limit: 0}, callback);
}
}
finishOpenRevs();
} else {
return cb(pouchdbErrors.createError(pouchdbErrors.UNKNOWN_ERROR, 'function_clause'));
}
}
return; // open_revs does not like other options
}
return this._get(id, opts, function (err, result) {
if (err) {
err.docId = id;
return cb(err);
}
return this._allDocs(opts, callback);
}).bind(this);
var doc = result.doc;
var metadata = result.metadata;
var ctx = result.ctx;
this.close = pouchdbUtils.adapterFun('close', function (callback) {
this._closed = true;
this.emit('closed');
return this._close(callback);
}).bind(this);
if (opts.conflicts) {
var conflicts = pouchdbMerge.collectConflicts(metadata);
if (conflicts.length) {
doc._conflicts = conflicts;
}
}
this.info = pouchdbUtils.adapterFun('info', function (callback) {
this._info((err, info) => {
if (err) {
return callback(err);
}
// assume we know better than the adapter, unless it informs us
info.db_name = info.db_name || this.name;
info.auto_compaction = !!(this.auto_compaction && !pouchdbUtils.isRemote(this));
info.adapter = this.adapter;
callback(null, info);
});
}).bind(this);
if (pouchdbMerge.isDeleted(metadata, doc._rev)) {
doc._deleted = true;
}
this.id = pouchdbUtils.adapterFun('id', function (callback) {
return this._id(callback);
}).bind(this);
if (opts.revs || opts.revs_info) {
var splittedRev = doc._rev.split('-');
var revNo = parseInt(splittedRev[0], 10);
var revHash = splittedRev[1];
this.bulkDocs = pouchdbUtils.adapterFun('bulkDocs', function (req, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
var paths = pouchdbMerge.rootToLeaf(metadata.rev_tree);
var path = null;
opts = opts || {};
for (var i = 0; i < paths.length; i++) {
var currentPath = paths[i];
var hashIndex = currentPath.ids.map(function (x) { return x.id; })
.indexOf(revHash);
var hashFoundAtRevPos = hashIndex === (revNo - 1);
if (Array.isArray(req)) {
req = {
docs: req
};
}
if (hashFoundAtRevPos || (!path && hashIndex !== -1)) {
path = currentPath;
}
if (!req || !req.docs || !Array.isArray(req.docs)) {
return callback(pouchdbErrors.createError(pouchdbErrors.MISSING_BULK_DOCS));
}
/* istanbul ignore if */
if (!path) {
err = new Error('invalid rev tree');
err.docId = id;
return cb(err);
for (var i = 0; i < req.docs.length; ++i) {
if (typeof req.docs[i] !== 'object' || Array.isArray(req.docs[i])) {
return callback(pouchdbErrors.createError(pouchdbErrors.NOT_AN_OBJECT));
}
}
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();
var attachmentError;
req.docs.forEach(function (doc) {
if (doc._attachments) {
Object.keys(doc._attachments).forEach(function (name) {
attachmentError = attachmentError || attachmentNameError(name);
if (!doc._attachments[name].content_type) {
pouchdbUtils.guardedConsole('warn', 'Attachment', name, 'on document', doc._id, 'is missing content_type');
}
});
}
});
if (opts.revs) {
doc._revisions = {
start: (path.pos + path.ids.length) - 1,
ids: path.ids.map(function (rev) {
return rev.id;
})
};
if (attachmentError) {
return callback(pouchdbErrors.createError(pouchdbErrors.BAD_REQUEST, attachmentError));
}
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 cb(null, doc);
}
Object.keys(attachments).forEach(function (key) {
this._getAttachment(doc._id, key, attachments[key], {
// Previously the revision handling was done in adapter.js
// getAttachment, however since idb-next doesnt we need to
// pass the rev through
rev: doc._rev,
binary: opts.binary,
ctx: ctx
}, function (err, data) {
var att = doc._attachments[key];
att.data = data;
delete att.stub;
delete att.length;
if (!--count) {
cb(null, doc);
}
});
}, self);
} else {
if (doc._attachments) {
for (var key in doc._attachments) {
/* istanbul ignore else */
if (Object.prototype.hasOwnProperty.call(doc._attachments, key)) {
doc._attachments[key].stub = true;
}
if (!('new_edits' in opts)) {
if ('new_edits' in req) {
opts.new_edits = req.new_edits;
} else {
opts.new_edits = true;
}
}
cb(null, doc);
}
});
});
// TODO: I dont like this, it forces an extra read for every
// attachment read and enforces a confusing api between
// adapter.js and the adapter implementation
AbstractPouchDB.prototype.getAttachment =
pouchdbUtils.adapterFun('getAttachment', function (docId, attachmentId, opts, callback) {
var self = this;
if (opts instanceof Function) {
callback = opts;
opts = {};
}
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(docId, attachmentId,
res.doc._attachments[attachmentId], opts, callback);
} else {
return callback(pouchdbErrors.createError(pouchdbErrors.MISSING_DOC));
}
});
});
AbstractPouchDB.prototype.allDocs =
pouchdbUtils.adapterFun('allDocs', function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
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(pouchdbErrors.createError(pouchdbErrors.QUERY_PARSE_ERROR,
'Query parameter `' + incompatibleOpt +
'` is not compatible with multi-get'
));
return;
}
if (!pouchdbUtils.isRemote(this)) {
allDocsKeysParse(opts);
if (opts.keys.length === 0) {
return this._allDocs({limit: 0}, callback);
var adapter = this;
if (!opts.new_edits && !pouchdbUtils.isRemote(adapter)) {
// ensure revisions of the same doc are sorted, so that
// the local adapter processes them correctly (#2935)
req.docs.sort(compareByIdThenRev);
}
}
}
return this._allDocs(opts, callback);
});
cleanDocs(req.docs);
AbstractPouchDB.prototype.changes = function (opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
// in the case of conflicts, we want to return the _ids to the user
// however, the underlying adapter may destroy the docs array, so
// create a copy here
var ids = req.docs.map(function (doc) {
return doc._id;
});
opts = opts || {};
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;
});
}
// add ids for error/conflict responses (not required for CouchDB)
if (!pouchdbUtils.isRemote(adapter)) {
for (var i = 0, l = res.length; i < l; i++) {
res[i].id = res[i].id || ids[i];
}
}
// By default set return_docs to false if the caller has opts.live = true,
// this will prevent us from collecting the set of changes indefinitely
// resulting in growing memory
opts.return_docs = ('return_docs' in opts) ? opts.return_docs : !opts.live;
callback(null, res);
});
}).bind(this);
return new Changes(this, opts, callback);
};
this.registerDependentDatabase = pouchdbUtils.adapterFun('registerDependentDatabase', function (dependentDb, callback) {
var dbOptions = pouchdbUtils.clone(this.__opts);
if (this.__opts.view_adapter) {
dbOptions.adapter = this.__opts.view_adapter;
}
AbstractPouchDB.prototype.close = pouchdbUtils.adapterFun('close', function (callback) {
this._closed = true;
this.emit('closed');
return this._close(callback);
});
var depDB = new this.constructor(dependentDb, dbOptions);
AbstractPouchDB.prototype.info = pouchdbUtils.adapterFun('info', function (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.name;
info.auto_compaction = !!(self.auto_compaction && !pouchdbUtils.isRemote(self));
info.adapter = self.adapter;
callback(null, info);
});
});
function diffFun(doc) {
doc.dependentDbs = doc.dependentDbs || {};
if (doc.dependentDbs[dependentDb]) {
return false; // no update required
}
doc.dependentDbs[dependentDb] = true;
return doc;
}
pouchdbUtils.upsert(this, '_local/_pouch_dependentDbs', diffFun).then(function () {
callback(null, {db: depDB});
}).catch(callback);
}).bind(this);
AbstractPouchDB.prototype.id = pouchdbUtils.adapterFun('id', function (callback) {
return this._id(callback);
});
this.destroy = pouchdbUtils.adapterFun('destroy', function (opts, callback) {
/* istanbul ignore next */
AbstractPouchDB.prototype.type = function () {
return (typeof this._type === 'function') ? this._type() : this.adapter;
};
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
AbstractPouchDB.prototype.bulkDocs =
pouchdbUtils.adapterFun('bulkDocs', function (req, opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
var usePrefix = 'use_prefix' in this ? this.use_prefix : true;
opts = opts || {};
const destroyDb = () => {
// call destroy method of the particular adaptor
this._destroy(opts, (err, resp) => {
if (err) {
return callback(err);
}
this._destroyed = true;
this.emit('destroyed');
callback(null, resp || { 'ok': true });
});
};
if (Array.isArray(req)) {
req = {
docs: req
};
}
if (pouchdbUtils.isRemote(this)) {
// no need to check for dependent DBs if it's a remote DB
return destroyDb();
}
if (!req || !req.docs || !Array.isArray(req.docs)) {
return callback(pouchdbErrors.createError(pouchdbErrors.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(pouchdbErrors.createError(pouchdbErrors.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 (!doc._attachments[name].content_type) {
pouchdbUtils.guardedConsole('warn', 'Attachment', name, 'on document', doc._id, 'is missing content_type');
this.get('_local/_pouch_dependentDbs', (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 = this.constructor;
var deletedMap = Object.keys(dependentDbs).map((name) => {
// use_prefix is only false in the browser
/* istanbul ignore next */
var trueName = usePrefix ?
name.replace(new RegExp('^' + PouchDB.prefix), '') : name;
return new PouchDB(trueName, this.__opts).destroy();
});
Promise.all(deletedMap).then(destroyDb, callback);
});
}
});
if (attachmentError) {
return callback(pouchdbErrors.createError(pouchdbErrors.BAD_REQUEST, attachmentError));
}).bind(this);
}
if (!('new_edits' in opts)) {
if ('new_edits' in req) {
opts.new_edits = req.new_edits;
} else {
opts.new_edits = true;
}
}
_compact(opts, callback) {
var changesOpts = {
return_docs: false,
last_seq: opts.last_seq || 0
};
var promises = [];
var adapter = this;
if (!opts.new_edits && !pouchdbUtils.isRemote(adapter)) {
// ensure revisions of the same doc are sorted, so that
// the local adapter processes them correctly (#2935)
req.docs.sort(compareByIdThenRev);
}
var taskId;
var compactedDocs = 0;
cleanDocs(req.docs);
const onChange = (row) => {
this.activeTasks.update(taskId, {
completed_items: ++compactedDocs
});
promises.push(this.compactDocument(row.id, 0));
};
const onError = (err) => {
this.activeTasks.remove(taskId, err);
callback(err);
};
const onComplete = (resp) => {
var lastSeq = resp.last_seq;
Promise.all(promises).then(() => {
return pouchdbUtils.upsert(this, '_local/compaction', (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(() => {
this.activeTasks.remove(taskId);
callback(null, {ok: true});
}).catch(onError);
};
// in the case of conflicts, we want to return the _ids to the user
// however, the underlying adapter may destroy the docs array, so
// create a copy here
var ids = req.docs.map(function (doc) {
return doc._id;
});
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;
this.info().then((info) => {
taskId = this.activeTasks.add({
name: 'database_compaction',
total_items: info.update_seq - changesOpts.last_seq,
});
}
// add ids for error/conflict responses (not required for CouchDB)
if (!pouchdbUtils.isRemote(adapter)) {
for (var i = 0, l = res.length; i < l; i++) {
res[i].id = res[i].id || ids[i];
}
}
callback(null, res);
});
});
AbstractPouchDB.prototype.registerDependentDatabase =
pouchdbUtils.adapterFun('registerDependentDatabase', function (dependentDb,
callback) {
var dbOptions = pouchdbUtils.clone(this.__opts);
if (this.__opts.view_adapter) {
dbOptions.adapter = this.__opts.view_adapter;
this.changes(changesOpts)
.on('change', onChange)
.on('complete', onComplete)
.on('error', onError);
});
}
var depDB = new this.constructor(dependentDb, dbOptions);
function diffFun(doc) {
doc.dependentDbs = doc.dependentDbs || {};
if (doc.dependentDbs[dependentDb]) {
return false; // no update required
changes(opts, callback) {
if (typeof opts === 'function') {
callback = opts;
opts = {};
}
doc.dependentDbs[dependentDb] = true;
return doc;
}
pouchdbUtils.upsert(this, '_local/_pouch_dependentDbs', diffFun)
.then(function () {
callback(null, {db: depDB});
}).catch(callback);
});
AbstractPouchDB.prototype.destroy =
pouchdbUtils.adapterFun('destroy', function (opts, callback) {
opts = opts || {};
if (typeof opts === 'function') {
callback = opts;
opts = {};
// By default set return_docs to false if the caller has opts.live = true,
// this will prevent us from collecting the set of changes indefinitely
// resulting in growing memory
opts.return_docs = ('return_docs' in opts) ? opts.return_docs : !opts.live;
return new Changes(this, 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._destroyed = true;
self.emit('destroyed');
callback(null, resp || { 'ok': true });
});
type() {
return (typeof this._type === 'function') ? this._type() : this.adapter;
}
}
if (pouchdbUtils.isRemote(self)) {
// no need to check for dependent DBs if it's a remote DB
return destroyDb();
// The abstract purge implementation expects a doc id and the rev of a leaf node in that doc.
// It will return errors if the rev doesn’t exist or isn’t a leaf.
AbstractPouchDB.prototype.purge = pouchdbUtils.adapterFun('_purge', function (docId, rev, callback) {
if (typeof this._purge === 'undefined') {
return callback(pouchdbErrors.createError(pouchdbErrors.UNKNOWN_ERROR, 'Purge is not implemented in the ' + this.adapter + ' adapter.'));
}
var self = this;
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();
self._getRevisionTree(docId, (error, revs) => {
if (error) {
return callback(error);
}
if (!revs) {
return callback(pouchdbErrors.createError(pouchdbErrors.MISSING_DOC));
}
let path;
try {
path = pouchdbMerge.findPathToLeaf(revs, rev);
} catch (error) {
return callback(error.message || error);
}
self._purge(docId, path, (error, result) => {
if (error) {
return callback(error);
} else {
appendPurgeSeq(self, docId, rev).then(function () {
return callback(null, result);
});
}
}
var dependentDbs = localDoc.dependentDbs;
var PouchDB = self.constructor;
var deletedMap = Object.keys(dependentDbs).map(function (name) {
// use_prefix is only false in the browser
/* istanbul ignore next */
var trueName = usePrefix ?
name.replace(new RegExp('^' + PouchDB.prefix), '') : name;
return new PouchDB(trueName, self.__opts).destroy();
});
Promise.all(deletedMap).then(destroyDb, callback);
});
});
function TaskQueue() {
this.isReady = false;
this.failed = false;
this.queue = [];
}
class TaskQueue {
constructor() {
this.isReady = false;
this.failed = false;
this.queue = [];
}
TaskQueue.prototype.execute = function () {
var fun;
if (this.failed) {
while ((fun = this.queue.shift())) {
fun(this.failed);
execute() {
var fun;
if (this.failed) {
while ((fun = this.queue.shift())) {
fun(this.failed);
}
} else {
while ((fun = this.queue.shift())) {
fun();
}
}
} else {
while ((fun = this.queue.shift())) {
fun();
}
}
};
TaskQueue.prototype.fail = function (err) {
this.failed = err;
this.execute();
};
fail(err) {
this.failed = err;
this.execute();
}
TaskQueue.prototype.ready = function (db) {
this.isReady = true;
this.db = db;
this.execute();
};
TaskQueue.prototype.addTask = function (fun) {
this.queue.push(fun);
if (this.failed) {
ready(db) {
this.isReady = true;
this.db = db;
this.execute();
}
};
addTask(fun) {
this.queue.push(fun);
if (this.failed) {
this.execute();
}
}
}
function parseAdapter(name, opts) {

@@ -1231,2 +1281,19 @@ var match = name.match(/([a-z-]*):\/\/(.*)/);

function inherits(A, B) {
A.prototype = Object.create(B.prototype, {
constructor: { value: A }
});
}
function createClass(parent, init) {
let klass = function (...args) {
if (!(this instanceof klass)) {
return new klass(...args);
}
init.apply(this, args);
};
inherits(klass, parent);
return klass;
}
// OK, so here's the deal. Consider this code:

@@ -1261,70 +1328,120 @@ // var db1 = new PouchDB('foo');

inherits(PouchDB, AbstractPouchDB);
function PouchDB(name, opts) {
// In Node our test suite only tests this for PouchAlt unfortunately
/* istanbul ignore if */
if (!(this instanceof PouchDB)) {
return new PouchDB(name, opts);
class PouchInternal extends AbstractPouchDB {
constructor(name, opts) {
super();
this._setup(name, opts);
}
var self = this;
opts = opts || {};
_setup(name, opts) {
super._setup();
opts = opts || {};
if (name && typeof name === 'object') {
opts = name;
name = opts.name;
delete opts.name;
}
if (name && typeof name === 'object') {
opts = name;
name = opts.name;
delete opts.name;
}
if (opts.deterministic_revs === undefined) {
opts.deterministic_revs = true;
}
if (opts.deterministic_revs === undefined) {
opts.deterministic_revs = true;
}
this.__opts = opts = pouchdbUtils.clone(opts);
this.__opts = opts = pouchdbUtils.clone(opts);
self.auto_compaction = opts.auto_compaction;
self.prefix = PouchDB.prefix;
this.auto_compaction = opts.auto_compaction;
this.purged_infos_limit = opts.purged_infos_limit || 1000;
this.prefix = PouchDB.prefix;
if (typeof name !== 'string') {
throw new Error('Missing/invalid DB name');
}
if (typeof name !== 'string') {
throw new Error('Missing/invalid DB name');
}
var prefixedName = (opts.prefix || '') + name;
var backend = parseAdapter(prefixedName, opts);
var prefixedName = (opts.prefix || '') + name;
var backend = parseAdapter(prefixedName, opts);
opts.name = backend.name;
opts.adapter = opts.adapter || backend.adapter;
opts.name = backend.name;
opts.adapter = opts.adapter || backend.adapter;
self.name = name;
self._adapter = opts.adapter;
PouchDB.emit('debug', ['adapter', 'Picked adapter: ', opts.adapter]);
this.name = name;
this._adapter = opts.adapter;
PouchDB.emit('debug', ['adapter', 'Picked adapter: ', opts.adapter]);
if (!PouchDB.adapters[opts.adapter] ||
!PouchDB.adapters[opts.adapter].valid()) {
throw new Error('Invalid Adapter: ' + opts.adapter);
}
if (!PouchDB.adapters[opts.adapter] ||
!PouchDB.adapters[opts.adapter].valid()) {
throw new Error('Invalid Adapter: ' + opts.adapter);
}
if (opts.view_adapter) {
if (!PouchDB.adapters[opts.view_adapter] ||
!PouchDB.adapters[opts.view_adapter].valid()) {
throw new Error('Invalid View Adapter: ' + opts.view_adapter);
if (opts.view_adapter) {
if (!PouchDB.adapters[opts.view_adapter] ||
!PouchDB.adapters[opts.view_adapter].valid()) {
throw new Error('Invalid View Adapter: ' + opts.view_adapter);
}
}
this.taskqueue = new TaskQueue();
this.adapter = opts.adapter;
PouchDB.adapters[opts.adapter].call(this, opts, (err) => {
if (err) {
return this.taskqueue.fail(err);
}
prepareForDestruction(this);
this.emit('created', this);
PouchDB.emit('created', this.name);
this.taskqueue.ready(this);
});
}
}
AbstractPouchDB.call(self);
self.taskqueue = new TaskQueue();
const PouchDB = createClass(PouchInternal, function (name, opts) {
PouchInternal.prototype._setup.call(this, name, opts);
});
self.adapter = opts.adapter;
class ActiveTasks {
constructor() {
this.tasks = {};
}
PouchDB.adapters[opts.adapter].call(self, opts, function (err) {
if (err) {
return self.taskqueue.fail(err);
}
prepareForDestruction(self);
list() {
return Object.values(this.tasks);
}
self.emit('created', self);
PouchDB.emit('created', self.name);
self.taskqueue.ready(self);
});
add(task) {
const id = uuid.v4();
this.tasks[id] = {
id,
name: task.name,
total_items: task.total_items,
created_at: new Date().toJSON()
};
return id;
}
get(id) {
return this.tasks[id];
}
/* eslint-disable no-unused-vars */
remove(id, reason) {
delete this.tasks[id];
return this.tasks;
}
update(id, updatedTask) {
const task = this.tasks[id];
if (typeof task !== 'undefined') {
const mergedTask = {
id: task.id,
name: task.name,
created_at: task.created_at,
total_items: updatedTask.total_items || task.total_items,
completed_items: updatedTask.completed_items || task.completed_items,
updated_at: new Date().toJSON()
};
this.tasks[id] = mergedTask;
}
return this.tasks;
}
}

@@ -1417,7 +1534,3 @@

PouchDB.defaults = function (defaultOpts) {
function PouchAlt(name, opts) {
if (!(this instanceof PouchAlt)) {
return new PouchAlt(name, opts);
}
let PouchWithDefaults = createClass(PouchDB, function (name, opts) {
opts = opts || {};

@@ -1431,12 +1544,10 @@

opts = pouchdbUtils.assign({}, PouchAlt.__defaults, opts);
opts = pouchdbUtils.assign({}, PouchWithDefaults.__defaults, opts);
PouchDB.call(this, name, opts);
}
});
inherits(PouchAlt, PouchDB);
PouchAlt.preferredAdapters = PouchDB.preferredAdapters.slice();
PouchWithDefaults.preferredAdapters = PouchDB.preferredAdapters.slice();
Object.keys(PouchDB).forEach(function (key) {
if (!(key in PouchAlt)) {
PouchAlt[key] = PouchDB[key];
if (!(key in PouchWithDefaults)) {
PouchWithDefaults[key] = PouchDB[key];
}

@@ -1447,5 +1558,5 @@ });

// https://github.com/pouchdb/pouchdb/issues/5922
PouchAlt.__defaults = pouchdbUtils.assign({}, this.__defaults, defaultOpts);
PouchWithDefaults.__defaults = pouchdbUtils.assign({}, this.__defaults, defaultOpts);
return PouchAlt;
return PouchWithDefaults;
};

@@ -1457,4 +1568,6 @@

PouchDB.prototype.activeTasks = PouchDB.activeTasks = new ActiveTasks();
// managed automatically by set-version.js
var version = "7.3.1";
var version = "8.0.0";

@@ -1461,0 +1574,0 @@ // TODO: remove from pouchdb-core (breaking)

{
"name": "pouchdb-core",
"version": "7.3.1",
"version": "8.0.0",
"description": "The core of PouchDB as a standalone package.",

@@ -16,10 +16,9 @@ "main": "./lib/index.js",

"dependencies": {
"argsarray": "0.0.1",
"inherits": "2.0.4",
"pouchdb-changes-filter": "7.3.1",
"pouchdb-collections": "7.3.1",
"pouchdb-errors": "7.3.1",
"pouchdb-fetch": "7.3.1",
"pouchdb-merge": "7.3.1",
"pouchdb-utils": "7.3.1"
"pouchdb-changes-filter": "8.0.0",
"pouchdb-collections": "8.0.0",
"pouchdb-errors": "8.0.0",
"pouchdb-fetch": "8.0.0",
"pouchdb-merge": "8.0.0",
"pouchdb-utils": "8.0.0",
"uuid": "8.3.2"
},

@@ -26,0 +25,0 @@ "module": "./lib/index.es.js",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc