pouchdb-core
Advanced tools
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) |
1995
lib/index.js
@@ -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", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
103177
7
2778
+ Addeduuid@8.3.2
+ Addedpouchdb-binary-utils@8.0.0(transitive)
+ Addedpouchdb-changes-filter@8.0.0(transitive)
+ Addedpouchdb-collate@8.0.0(transitive)
+ Addedpouchdb-collections@8.0.0(transitive)
+ Addedpouchdb-errors@8.0.0(transitive)
+ Addedpouchdb-fetch@8.0.0(transitive)
+ Addedpouchdb-md5@8.0.0(transitive)
+ Addedpouchdb-merge@8.0.0(transitive)
+ Addedpouchdb-selector-core@8.0.0(transitive)
+ Addedpouchdb-utils@8.0.0(transitive)
- Removedargsarray@0.0.1
- Removedinherits@2.0.4
- Removedargsarray@0.0.1(transitive)
- Removedinherits@2.0.4(transitive)
- Removedpouchdb-binary-utils@7.3.1(transitive)
- Removedpouchdb-changes-filter@7.3.1(transitive)
- Removedpouchdb-collate@7.3.1(transitive)
- Removedpouchdb-collections@7.3.1(transitive)
- Removedpouchdb-errors@7.3.1(transitive)
- Removedpouchdb-fetch@7.3.1(transitive)
- Removedpouchdb-md5@7.3.1(transitive)
- Removedpouchdb-merge@7.3.1(transitive)
- Removedpouchdb-selector-core@7.3.1(transitive)
- Removedpouchdb-utils@7.3.1(transitive)
Updatedpouchdb-changes-filter@8.0.0
Updatedpouchdb-collections@8.0.0
Updatedpouchdb-errors@8.0.0
Updatedpouchdb-fetch@8.0.0
Updatedpouchdb-merge@8.0.0
Updatedpouchdb-utils@8.0.0