UNPKG

286 kBJavaScriptView Raw
1import cloneBuffer from 'clone-buffer';
2import getArguments from 'argsarray';
3import events, { EventEmitter } from 'events';
4import inherits from 'inherits';
5import crypto from 'crypto';
6import uuidV4 from 'uuid';
7import nodeFetch from 'node-fetch';
8import fetchCookie from 'fetch-cookie';
9import vm from 'vm';
10import ltgt from 'ltgt';
11import ReadableStreamCore from 'readable-stream';
12import Codec from 'level-codec';
13import bufferFrom from 'buffer-from';
14import vuvuzela from 'vuvuzela';
15import levelup from 'levelup';
16import { obj } from 'through2';
17import Deque from 'double-ended-queue';
18import fs from 'fs';
19import path from 'path';
20import level from 'level';
21import LevelWriteStream from 'level-write-stream';
22
23function isBinaryObject(object) {
24 return object instanceof Buffer;
25}
26
27// most of this is borrowed from lodash.isPlainObject:
28// https://github.com/fis-components/lodash.isplainobject/
29// blob/29c358140a74f252aeb08c9eb28bef86f2217d4a/index.js
30
31var funcToString = Function.prototype.toString;
32var objectCtorString = funcToString.call(Object);
33
34function isPlainObject(value) {
35 var proto = Object.getPrototypeOf(value);
36 /* istanbul ignore if */
37 if (proto === null) { // not sure when this happens, but I guess it can
38 return true;
39 }
40 var Ctor = proto.constructor;
41 return (typeof Ctor == 'function' &&
42 Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
43}
44
45function clone(object) {
46 var newObject;
47 var i;
48 var len;
49
50 if (!object || typeof object !== 'object') {
51 return object;
52 }
53
54 if (Array.isArray(object)) {
55 newObject = [];
56 for (i = 0, len = object.length; i < len; i++) {
57 newObject[i] = clone(object[i]);
58 }
59 return newObject;
60 }
61
62 // special case: to avoid inconsistencies between IndexedDB
63 // and other backends, we automatically stringify Dates
64 if (object instanceof Date) {
65 return object.toISOString();
66 }
67
68 if (isBinaryObject(object)) {
69 return cloneBuffer(object);
70 }
71
72 if (!isPlainObject(object)) {
73 return object; // don't clone objects like Workers
74 }
75
76 newObject = {};
77 for (i in object) {
78 /* istanbul ignore else */
79 if (Object.prototype.hasOwnProperty.call(object, i)) {
80 var value = clone(object[i]);
81 if (typeof value !== 'undefined') {
82 newObject[i] = value;
83 }
84 }
85 }
86 return newObject;
87}
88
89function once(fun) {
90 var called = false;
91 return getArguments(function (args) {
92 /* istanbul ignore if */
93 if (called) {
94 // this is a smoke test and should never actually happen
95 throw new Error('once called more than once');
96 } else {
97 called = true;
98 fun.apply(this, args);
99 }
100 });
101}
102
103function toPromise(func) {
104 //create the function we will be returning
105 return getArguments(function (args) {
106 // Clone arguments
107 args = clone(args);
108 var self = this;
109 // if the last argument is a function, assume its a callback
110 var usedCB = (typeof args[args.length - 1] === 'function') ? args.pop() : false;
111 var promise = new Promise(function (fulfill, reject) {
112 var resp;
113 try {
114 var callback = once(function (err, mesg) {
115 if (err) {
116 reject(err);
117 } else {
118 fulfill(mesg);
119 }
120 });
121 // create a callback for this invocation
122 // apply the function in the orig context
123 args.push(callback);
124 resp = func.apply(self, args);
125 if (resp && typeof resp.then === 'function') {
126 fulfill(resp);
127 }
128 } catch (e) {
129 reject(e);
130 }
131 });
132 // if there is a callback, call it back
133 if (usedCB) {
134 promise.then(function (result) {
135 usedCB(null, result);
136 }, usedCB);
137 }
138 return promise;
139 });
140}
141
142function logApiCall(self, name, args) {
143 /* istanbul ignore if */
144 if (self.constructor.listeners('debug').length) {
145 var logArgs = ['api', self.name, name];
146 for (var i = 0; i < args.length - 1; i++) {
147 logArgs.push(args[i]);
148 }
149 self.constructor.emit('debug', logArgs);
150
151 // override the callback itself to log the response
152 var origCallback = args[args.length - 1];
153 args[args.length - 1] = function (err, res) {
154 var responseArgs = ['api', self.name, name];
155 responseArgs = responseArgs.concat(
156 err ? ['error', err] : ['success', res]
157 );
158 self.constructor.emit('debug', responseArgs);
159 origCallback(err, res);
160 };
161 }
162}
163
164function adapterFun(name, callback) {
165 return toPromise(getArguments(function (args) {
166 if (this._closed) {
167 return Promise.reject(new Error('database is closed'));
168 }
169 if (this._destroyed) {
170 return Promise.reject(new Error('database is destroyed'));
171 }
172 var self = this;
173 logApiCall(self, name, args);
174 if (!this.taskqueue.isReady) {
175 return new Promise(function (fulfill, reject) {
176 self.taskqueue.addTask(function (failed) {
177 if (failed) {
178 reject(failed);
179 } else {
180 fulfill(self[name].apply(self, args));
181 }
182 });
183 });
184 }
185 return callback.apply(this, args);
186 }));
187}
188
189function mangle(key) {
190 return '$' + key;
191}
192function unmangle(key) {
193 return key.substring(1);
194}
195function Map$1() {
196 this._store = {};
197}
198Map$1.prototype.get = function (key) {
199 var mangled = mangle(key);
200 return this._store[mangled];
201};
202Map$1.prototype.set = function (key, value) {
203 var mangled = mangle(key);
204 this._store[mangled] = value;
205 return true;
206};
207Map$1.prototype.has = function (key) {
208 var mangled = mangle(key);
209 return mangled in this._store;
210};
211Map$1.prototype.delete = function (key) {
212 var mangled = mangle(key);
213 var res = mangled in this._store;
214 delete this._store[mangled];
215 return res;
216};
217Map$1.prototype.forEach = function (cb) {
218 var keys = Object.keys(this._store);
219 for (var i = 0, len = keys.length; i < len; i++) {
220 var key = keys[i];
221 var value = this._store[key];
222 key = unmangle(key);
223 cb(value, key);
224 }
225};
226Object.defineProperty(Map$1.prototype, 'size', {
227 get: function () {
228 return Object.keys(this._store).length;
229 }
230});
231
232function Set$1(array) {
233 this._store = new Map$1();
234
235 // init with an array
236 if (array && Array.isArray(array)) {
237 for (var i = 0, len = array.length; i < len; i++) {
238 this.add(array[i]);
239 }
240 }
241}
242Set$1.prototype.add = function (key) {
243 return this._store.set(key, true);
244};
245Set$1.prototype.has = function (key) {
246 return this._store.has(key);
247};
248Set$1.prototype.forEach = function (cb) {
249 this._store.forEach(function (value, key) {
250 cb(key);
251 });
252};
253Object.defineProperty(Set$1.prototype, 'size', {
254 get: function () {
255 return this._store.size;
256 }
257});
258
259/* global Map,Set,Symbol */
260// Based on https://kangax.github.io/compat-table/es6/ we can sniff out
261// incomplete Map/Set implementations which would otherwise cause our tests to fail.
262// Notably they fail in IE11 and iOS 8.4, which this prevents.
263function supportsMapAndSet() {
264 if (typeof Symbol === 'undefined' || typeof Map === 'undefined' || typeof Set === 'undefined') {
265 return false;
266 }
267 var prop = Object.getOwnPropertyDescriptor(Map, Symbol.species);
268 return prop && 'get' in prop && Map[Symbol.species] === Map;
269}
270
271// based on https://github.com/montagejs/collections
272
273var ExportedSet;
274var ExportedMap;
275
276{
277 if (supportsMapAndSet()) { // prefer built-in Map/Set
278 ExportedSet = Set;
279 ExportedMap = Map;
280 } else { // fall back to our polyfill
281 ExportedSet = Set$1;
282 ExportedMap = Map$1;
283 }
284}
285
286// like underscore/lodash _.pick()
287function pick(obj$$1, arr) {
288 var res = {};
289 for (var i = 0, len = arr.length; i < len; i++) {
290 var prop = arr[i];
291 if (prop in obj$$1) {
292 res[prop] = obj$$1[prop];
293 }
294 }
295 return res;
296}
297
298// Most browsers throttle concurrent requests at 6, so it's silly
299// to shim _bulk_get by trying to launch potentially hundreds of requests
300// and then letting the majority time out. We can handle this ourselves.
301var MAX_NUM_CONCURRENT_REQUESTS = 6;
302
303function identityFunction(x) {
304 return x;
305}
306
307function formatResultForOpenRevsGet(result) {
308 return [{
309 ok: result
310 }];
311}
312
313// shim for P/CouchDB adapters that don't directly implement _bulk_get
314function bulkGet(db, opts, callback) {
315 var requests = opts.docs;
316
317 // consolidate into one request per doc if possible
318 var requestsById = new ExportedMap();
319 requests.forEach(function (request) {
320 if (requestsById.has(request.id)) {
321 requestsById.get(request.id).push(request);
322 } else {
323 requestsById.set(request.id, [request]);
324 }
325 });
326
327 var numDocs = requestsById.size;
328 var numDone = 0;
329 var perDocResults = new Array(numDocs);
330
331 function collapseResultsAndFinish() {
332 var results = [];
333 perDocResults.forEach(function (res) {
334 res.docs.forEach(function (info) {
335 results.push({
336 id: res.id,
337 docs: [info]
338 });
339 });
340 });
341 callback(null, {results: results});
342 }
343
344 function checkDone() {
345 if (++numDone === numDocs) {
346 collapseResultsAndFinish();
347 }
348 }
349
350 function gotResult(docIndex, id, docs) {
351 perDocResults[docIndex] = {id: id, docs: docs};
352 checkDone();
353 }
354
355 var allRequests = [];
356 requestsById.forEach(function (value, key) {
357 allRequests.push(key);
358 });
359
360 var i = 0;
361
362 function nextBatch() {
363
364 if (i >= allRequests.length) {
365 return;
366 }
367
368 var upTo = Math.min(i + MAX_NUM_CONCURRENT_REQUESTS, allRequests.length);
369 var batch = allRequests.slice(i, upTo);
370 processBatch(batch, i);
371 i += batch.length;
372 }
373
374 function processBatch(batch, offset) {
375 batch.forEach(function (docId, j) {
376 var docIdx = offset + j;
377 var docRequests = requestsById.get(docId);
378
379 // just use the first request as the "template"
380 // TODO: The _bulk_get API allows for more subtle use cases than this,
381 // but for now it is unlikely that there will be a mix of different
382 // "atts_since" or "attachments" in the same request, since it's just
383 // replicate.js that is using this for the moment.
384 // Also, atts_since is aspirational, since we don't support it yet.
385 var docOpts = pick(docRequests[0], ['atts_since', 'attachments']);
386 docOpts.open_revs = docRequests.map(function (request) {
387 // rev is optional, open_revs disallowed
388 return request.rev;
389 });
390
391 // remove falsey / undefined revisions
392 docOpts.open_revs = docOpts.open_revs.filter(identityFunction);
393
394 var formatResult = identityFunction;
395
396 if (docOpts.open_revs.length === 0) {
397 delete docOpts.open_revs;
398
399 // when fetching only the "winning" leaf,
400 // transform the result so it looks like an open_revs
401 // request
402 formatResult = formatResultForOpenRevsGet;
403 }
404
405 // globally-supplied options
406 ['revs', 'attachments', 'binary', 'ajax', 'latest'].forEach(function (param) {
407 if (param in opts) {
408 docOpts[param] = opts[param];
409 }
410 });
411 db.get(docId, docOpts, function (err, res) {
412 var result;
413 /* istanbul ignore if */
414 if (err) {
415 result = [{error: err}];
416 } else {
417 result = formatResult(res);
418 }
419 gotResult(docIdx, docId, result);
420 nextBatch();
421 });
422 });
423 }
424
425 nextBatch();
426
427}
428
429// in Node of course this is false
430function hasLocalStorage() {
431 return false;
432}
433
434function nextTick(fn) {
435 process.nextTick(fn);
436}
437
438inherits(Changes, EventEmitter);
439
440/* istanbul ignore next */
441function attachBrowserEvents(self) {
442 if (hasLocalStorage()) {
443 addEventListener("storage", function (e) {
444 self.emit(e.key);
445 });
446 }
447}
448
449function Changes() {
450 EventEmitter.call(this);
451 this._listeners = {};
452
453 attachBrowserEvents(this);
454}
455Changes.prototype.addListener = function (dbName, id, db, opts) {
456 /* istanbul ignore if */
457 if (this._listeners[id]) {
458 return;
459 }
460 var self = this;
461 var inprogress = false;
462 function eventFunction() {
463 /* istanbul ignore if */
464 if (!self._listeners[id]) {
465 return;
466 }
467 if (inprogress) {
468 inprogress = 'waiting';
469 return;
470 }
471 inprogress = true;
472 var changesOpts = pick(opts, [
473 'style', 'include_docs', 'attachments', 'conflicts', 'filter',
474 'doc_ids', 'view', 'since', 'query_params', 'binary', 'return_docs'
475 ]);
476
477 /* istanbul ignore next */
478 function onError() {
479 inprogress = false;
480 }
481
482 db.changes(changesOpts).on('change', function (c) {
483 if (c.seq > opts.since && !opts.cancelled) {
484 opts.since = c.seq;
485 opts.onChange(c);
486 }
487 }).on('complete', function () {
488 if (inprogress === 'waiting') {
489 nextTick(eventFunction);
490 }
491 inprogress = false;
492 }).on('error', onError);
493 }
494 this._listeners[id] = eventFunction;
495 this.on(dbName, eventFunction);
496};
497
498Changes.prototype.removeListener = function (dbName, id) {
499 /* istanbul ignore if */
500 if (!(id in this._listeners)) {
501 return;
502 }
503 EventEmitter.prototype.removeListener.call(this, dbName,
504 this._listeners[id]);
505 delete this._listeners[id];
506};
507
508
509/* istanbul ignore next */
510Changes.prototype.notifyLocalWindows = function (dbName) {
511 //do a useless change on a storage thing
512 //in order to get other windows's listeners to activate
513 if (hasLocalStorage()) {
514 localStorage[dbName] = (localStorage[dbName] === "a") ? "b" : "a";
515 }
516};
517
518Changes.prototype.notify = function (dbName) {
519 this.emit(dbName);
520 this.notifyLocalWindows(dbName);
521};
522
523function guardedConsole(method) {
524 /* istanbul ignore else */
525 if (typeof console !== 'undefined' && typeof console[method] === 'function') {
526 var args = Array.prototype.slice.call(arguments, 1);
527 console[method].apply(console, args);
528 }
529}
530
531function randomNumber(min, max) {
532 var maxTimeout = 600000; // Hard-coded default of 10 minutes
533 min = parseInt(min, 10) || 0;
534 max = parseInt(max, 10);
535 if (max !== max || max <= min) {
536 max = (min || 1) << 1; //doubling
537 } else {
538 max = max + 1;
539 }
540 // In order to not exceed maxTimeout, pick a random value between half of maxTimeout and maxTimeout
541 if (max > maxTimeout) {
542 min = maxTimeout >> 1; // divide by two
543 max = maxTimeout;
544 }
545 var ratio = Math.random();
546 var range = max - min;
547
548 return ~~(range * ratio + min); // ~~ coerces to an int, but fast.
549}
550
551function defaultBackOff(min) {
552 var max = 0;
553 if (!min) {
554 max = 2000;
555 }
556 return randomNumber(min, max);
557}
558
559// We assume Node users don't need to see this warning
560var res = function () {};
561
562var assign;
563{
564 if (typeof Object.assign === 'function') {
565 assign = Object.assign;
566 } else {
567 // lite Object.assign polyfill based on
568 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
569 assign = function (target) {
570 var to = Object(target);
571
572 for (var index = 1; index < arguments.length; index++) {
573 var nextSource = arguments[index];
574
575 if (nextSource != null) { // Skip over if undefined or null
576 for (var nextKey in nextSource) {
577 // Avoid bugs when hasOwnProperty is shadowed
578 if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
579 to[nextKey] = nextSource[nextKey];
580 }
581 }
582 }
583 }
584 return to;
585 };
586 }
587}
588
589var $inject_Object_assign = assign;
590
591inherits(PouchError, Error);
592
593function PouchError(status, error, reason) {
594 Error.call(this, reason);
595 this.status = status;
596 this.name = error;
597 this.message = reason;
598 this.error = true;
599}
600
601PouchError.prototype.toString = function () {
602 return JSON.stringify({
603 status: this.status,
604 name: this.name,
605 message: this.message,
606 reason: this.reason
607 });
608};
609
610var UNAUTHORIZED = new PouchError(401, 'unauthorized', "Name or password is incorrect.");
611var MISSING_BULK_DOCS = new PouchError(400, 'bad_request', "Missing JSON list of 'docs'");
612var MISSING_DOC = new PouchError(404, 'not_found', 'missing');
613var REV_CONFLICT = new PouchError(409, 'conflict', 'Document update conflict');
614var INVALID_ID = new PouchError(400, 'bad_request', '_id field must contain a string');
615var MISSING_ID = new PouchError(412, 'missing_id', '_id is required for puts');
616var RESERVED_ID = new PouchError(400, 'bad_request', 'Only reserved document ids may start with underscore.');
617var NOT_OPEN = new PouchError(412, 'precondition_failed', 'Database not open');
618var UNKNOWN_ERROR = new PouchError(500, 'unknown_error', 'Database encountered an unknown error');
619var BAD_ARG = new PouchError(500, 'badarg', 'Some query argument is invalid');
620var INVALID_REQUEST = new PouchError(400, 'invalid_request', 'Request was invalid');
621var QUERY_PARSE_ERROR = new PouchError(400, 'query_parse_error', 'Some query parameter is invalid');
622var DOC_VALIDATION = new PouchError(500, 'doc_validation', 'Bad special document member');
623var BAD_REQUEST = new PouchError(400, 'bad_request', 'Something wrong with the request');
624var NOT_AN_OBJECT = new PouchError(400, 'bad_request', 'Document must be a JSON object');
625var DB_MISSING = new PouchError(404, 'not_found', 'Database not found');
626var IDB_ERROR = new PouchError(500, 'indexed_db_went_bad', 'unknown');
627var WSQ_ERROR = new PouchError(500, 'web_sql_went_bad', 'unknown');
628var LDB_ERROR = new PouchError(500, 'levelDB_went_went_bad', 'unknown');
629var FORBIDDEN = new PouchError(403, 'forbidden', 'Forbidden by design doc validate_doc_update function');
630var INVALID_REV = new PouchError(400, 'bad_request', 'Invalid rev format');
631var FILE_EXISTS = new PouchError(412, 'file_exists', 'The database could not be created, the file already exists.');
632var MISSING_STUB = new PouchError(412, 'missing_stub', 'A pre-existing attachment stub wasn\'t found');
633var INVALID_URL = new PouchError(413, 'invalid_url', 'Provided URL is invalid');
634
635function createError(error, reason) {
636 function CustomPouchError(reason) {
637 // inherit error properties from our parent error manually
638 // so as to allow proper JSON parsing.
639 /* jshint ignore:start */
640 for (var p in error) {
641 if (typeof error[p] !== 'function') {
642 this[p] = error[p];
643 }
644 }
645 /* jshint ignore:end */
646 if (reason !== undefined) {
647 this.reason = reason;
648 }
649 }
650 CustomPouchError.prototype = PouchError.prototype;
651 return new CustomPouchError(reason);
652}
653
654function generateErrorFromResponse(err) {
655
656 if (typeof err !== 'object') {
657 var data = err;
658 err = UNKNOWN_ERROR;
659 err.data = data;
660 }
661
662 if ('error' in err && err.error === 'conflict') {
663 err.name = 'conflict';
664 err.status = 409;
665 }
666
667 if (!('name' in err)) {
668 err.name = err.error || 'unknown';
669 }
670
671 if (!('status' in err)) {
672 err.status = 500;
673 }
674
675 if (!('message' in err)) {
676 err.message = err.message || err.reason;
677 }
678
679 return err;
680}
681
682function tryFilter(filter, doc, req) {
683 try {
684 return !filter(doc, req);
685 } catch (err) {
686 var msg = 'Filter function threw: ' + err.toString();
687 return createError(BAD_REQUEST, msg);
688 }
689}
690
691function filterChange(opts) {
692 var req = {};
693 var hasFilter = opts.filter && typeof opts.filter === 'function';
694 req.query = opts.query_params;
695
696 return function filter(change) {
697 if (!change.doc) {
698 // CSG sends events on the changes feed that don't have documents,
699 // this hack makes a whole lot of existing code robust.
700 change.doc = {};
701 }
702
703 var filterReturn = hasFilter && tryFilter(opts.filter, change.doc, req);
704
705 if (typeof filterReturn === 'object') {
706 return filterReturn;
707 }
708
709 if (filterReturn) {
710 return false;
711 }
712
713 if (!opts.include_docs) {
714 delete change.doc;
715 } else if (!opts.attachments) {
716 for (var att in change.doc._attachments) {
717 /* istanbul ignore else */
718 if (change.doc._attachments.hasOwnProperty(att)) {
719 change.doc._attachments[att].stub = true;
720 }
721 }
722 }
723 return true;
724 };
725}
726
727function flatten(arrs) {
728 var res = [];
729 for (var i = 0, len = arrs.length; i < len; i++) {
730 res = res.concat(arrs[i]);
731 }
732 return res;
733}
734
735// shim for Function.prototype.name,
736// for browsers that don't support it like IE
737
738/* istanbul ignore next */
739function f() {}
740
741var hasName = f.name;
742var res$1;
743
744// We dont run coverage in IE
745/* istanbul ignore else */
746if (hasName) {
747 res$1 = function (fun) {
748 return fun.name;
749 };
750} else {
751 res$1 = function (fun) {
752 var match = fun.toString().match(/^\s*function\s*(?:(\S+)\s*)?\(/);
753 if (match && match[1]) {
754 return match[1];
755 }
756 else {
757 return '';
758 }
759 };
760}
761
762var functionName = res$1;
763
764// Determine id an ID is valid
765// - invalid IDs begin with an underescore that does not begin '_design' or
766// '_local'
767// - any other string value is a valid id
768// Returns the specific error object for each case
769function invalidIdError(id) {
770 var err;
771 if (!id) {
772 err = createError(MISSING_ID);
773 } else if (typeof id !== 'string') {
774 err = createError(INVALID_ID);
775 } else if (/^_/.test(id) && !(/^_(design|local)/).test(id)) {
776 err = createError(RESERVED_ID);
777 }
778 if (err) {
779 throw err;
780 }
781}
782
783// Checks if a PouchDB object is "remote" or not. This is
784
785function isRemote(db) {
786 if (typeof db._remote === 'boolean') {
787 return db._remote;
788 }
789 /* istanbul ignore next */
790 if (typeof db.type === 'function') {
791 guardedConsole('warn',
792 'db.type() is deprecated and will be removed in ' +
793 'a future version of PouchDB');
794 return db.type() === 'http';
795 }
796 /* istanbul ignore next */
797 return false;
798}
799
800function listenerCount(ee, type) {
801 return 'listenerCount' in ee ? ee.listenerCount(type) :
802 EventEmitter.listenerCount(ee, type);
803}
804
805function parseDesignDocFunctionName(s) {
806 if (!s) {
807 return null;
808 }
809 var parts = s.split('/');
810 if (parts.length === 2) {
811 return parts;
812 }
813 if (parts.length === 1) {
814 return [s, s];
815 }
816 return null;
817}
818
819function normalizeDesignDocFunctionName(s) {
820 var normalized = parseDesignDocFunctionName(s);
821 return normalized ? normalized.join('/') : null;
822}
823
824// originally parseUri 1.2.2, now patched by us
825// (c) Steven Levithan <stevenlevithan.com>
826// MIT License
827var keys = ["source", "protocol", "authority", "userInfo", "user", "password",
828 "host", "port", "relative", "path", "directory", "file", "query", "anchor"];
829var qName ="queryKey";
830var qParser = /(?:^|&)([^&=]*)=?([^&]*)/g;
831
832// use the "loose" parser
833/* eslint maxlen: 0, no-useless-escape: 0 */
834var parser = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
835
836function parseUri(str) {
837 var m = parser.exec(str);
838 var uri = {};
839 var i = 14;
840
841 while (i--) {
842 var key = keys[i];
843 var value = m[i] || "";
844 var encoded = ['user', 'password'].indexOf(key) !== -1;
845 uri[key] = encoded ? decodeURIComponent(value) : value;
846 }
847
848 uri[qName] = {};
849 uri[keys[12]].replace(qParser, function ($0, $1, $2) {
850 if ($1) {
851 uri[qName][$1] = $2;
852 }
853 });
854
855 return uri;
856}
857
858// Based on https://github.com/alexdavid/scope-eval v0.0.3
859
860// this is essentially the "update sugar" function from daleharvey/pouchdb#1388
861// the diffFun tells us what delta to apply to the doc. it either returns
862// the doc, or false if it doesn't need to do an update after all
863function upsert(db, docId, diffFun) {
864 return new Promise(function (fulfill, reject) {
865 db.get(docId, function (err, doc) {
866 if (err) {
867 /* istanbul ignore next */
868 if (err.status !== 404) {
869 return reject(err);
870 }
871 doc = {};
872 }
873
874 // the user might change the _rev, so save it for posterity
875 var docRev = doc._rev;
876 var newDoc = diffFun(doc);
877
878 if (!newDoc) {
879 // if the diffFun returns falsy, we short-circuit as
880 // an optimization
881 return fulfill({updated: false, rev: docRev});
882 }
883
884 // users aren't allowed to modify these values,
885 // so reset them here
886 newDoc._id = docId;
887 newDoc._rev = docRev;
888 fulfill(tryAndPut(db, newDoc, diffFun));
889 });
890 });
891}
892
893function tryAndPut(db, doc, diffFun) {
894 return db.put(doc).then(function (res) {
895 return {
896 updated: true,
897 rev: res.rev
898 };
899 }, function (err) {
900 /* istanbul ignore next */
901 if (err.status !== 409) {
902 throw err;
903 }
904 return upsert(db, doc._id, diffFun);
905 });
906}
907
908function binaryMd5(data, callback) {
909 var base64 = crypto.createHash('md5').update(data, 'binary').digest('base64');
910 callback(base64);
911}
912
913function stringMd5(string) {
914 return crypto.createHash('md5').update(string, 'binary').digest('hex');
915}
916
917function rev$$1(doc, deterministic_revs) {
918 var clonedDoc = clone(doc);
919 if (!deterministic_revs) {
920 return uuidV4.v4().replace(/-/g, '').toLowerCase();
921 }
922
923 delete clonedDoc._rev_tree;
924 return stringMd5(JSON.stringify(clonedDoc));
925}
926
927var uuid = uuidV4.v4;
928
929// We fetch all leafs of the revision tree, and sort them based on tree length
930// and whether they were deleted, undeleted documents with the longest revision
931// tree (most edits) win
932// The final sort algorithm is slightly documented in a sidebar here:
933// http://guide.couchdb.org/draft/conflicts.html
934function winningRev(metadata) {
935 var winningId;
936 var winningPos;
937 var winningDeleted;
938 var toVisit = metadata.rev_tree.slice();
939 var node;
940 while ((node = toVisit.pop())) {
941 var tree = node.ids;
942 var branches = tree[2];
943 var pos = node.pos;
944 if (branches.length) { // non-leaf
945 for (var i = 0, len = branches.length; i < len; i++) {
946 toVisit.push({pos: pos + 1, ids: branches[i]});
947 }
948 continue;
949 }
950 var deleted = !!tree[1].deleted;
951 var id = tree[0];
952 // sort by deleted, then pos, then id
953 if (!winningId || (winningDeleted !== deleted ? winningDeleted :
954 winningPos !== pos ? winningPos < pos : winningId < id)) {
955 winningId = id;
956 winningPos = pos;
957 winningDeleted = deleted;
958 }
959 }
960
961 return winningPos + '-' + winningId;
962}
963
964// Pretty much all below can be combined into a higher order function to
965// traverse revisions
966// The return value from the callback will be passed as context to all
967// children of that node
968function traverseRevTree(revs, callback) {
969 var toVisit = revs.slice();
970
971 var node;
972 while ((node = toVisit.pop())) {
973 var pos = node.pos;
974 var tree = node.ids;
975 var branches = tree[2];
976 var newCtx =
977 callback(branches.length === 0, pos, tree[0], node.ctx, tree[1]);
978 for (var i = 0, len = branches.length; i < len; i++) {
979 toVisit.push({pos: pos + 1, ids: branches[i], ctx: newCtx});
980 }
981 }
982}
983
984function sortByPos(a, b) {
985 return a.pos - b.pos;
986}
987
988function collectLeaves(revs) {
989 var leaves = [];
990 traverseRevTree(revs, function (isLeaf, pos, id, acc, opts) {
991 if (isLeaf) {
992 leaves.push({rev: pos + "-" + id, pos: pos, opts: opts});
993 }
994 });
995 leaves.sort(sortByPos).reverse();
996 for (var i = 0, len = leaves.length; i < len; i++) {
997 delete leaves[i].pos;
998 }
999 return leaves;
1000}
1001
1002// returns revs of all conflicts that is leaves such that
1003// 1. are not deleted and
1004// 2. are different than winning revision
1005function collectConflicts(metadata) {
1006 var win = winningRev(metadata);
1007 var leaves = collectLeaves(metadata.rev_tree);
1008 var conflicts = [];
1009 for (var i = 0, len = leaves.length; i < len; i++) {
1010 var leaf = leaves[i];
1011 if (leaf.rev !== win && !leaf.opts.deleted) {
1012 conflicts.push(leaf.rev);
1013 }
1014 }
1015 return conflicts;
1016}
1017
1018// compact a tree by marking its non-leafs as missing,
1019// and return a list of revs to delete
1020function compactTree(metadata) {
1021 var revs = [];
1022 traverseRevTree(metadata.rev_tree, function (isLeaf, pos,
1023 revHash, ctx, opts) {
1024 if (opts.status === 'available' && !isLeaf) {
1025 revs.push(pos + '-' + revHash);
1026 opts.status = 'missing';
1027 }
1028 });
1029 return revs;
1030}
1031
1032// build up a list of all the paths to the leafs in this revision tree
1033function rootToLeaf(revs) {
1034 var paths = [];
1035 var toVisit = revs.slice();
1036 var node;
1037 while ((node = toVisit.pop())) {
1038 var pos = node.pos;
1039 var tree = node.ids;
1040 var id = tree[0];
1041 var opts = tree[1];
1042 var branches = tree[2];
1043 var isLeaf = branches.length === 0;
1044
1045 var history = node.history ? node.history.slice() : [];
1046 history.push({id: id, opts: opts});
1047 if (isLeaf) {
1048 paths.push({pos: (pos + 1 - history.length), ids: history});
1049 }
1050 for (var i = 0, len = branches.length; i < len; i++) {
1051 toVisit.push({pos: pos + 1, ids: branches[i], history: history});
1052 }
1053 }
1054 return paths.reverse();
1055}
1056
1057// for a better overview of what this is doing, read:
1058
1059function sortByPos$1(a, b) {
1060 return a.pos - b.pos;
1061}
1062
1063// classic binary search
1064function binarySearch(arr, item, comparator) {
1065 var low = 0;
1066 var high = arr.length;
1067 var mid;
1068 while (low < high) {
1069 mid = (low + high) >>> 1;
1070 if (comparator(arr[mid], item) < 0) {
1071 low = mid + 1;
1072 } else {
1073 high = mid;
1074 }
1075 }
1076 return low;
1077}
1078
1079// assuming the arr is sorted, insert the item in the proper place
1080function insertSorted(arr, item, comparator) {
1081 var idx = binarySearch(arr, item, comparator);
1082 arr.splice(idx, 0, item);
1083}
1084
1085// Turn a path as a flat array into a tree with a single branch.
1086// If any should be stemmed from the beginning of the array, that's passed
1087// in as the second argument
1088function pathToTree(path$$1, numStemmed) {
1089 var root;
1090 var leaf;
1091 for (var i = numStemmed, len = path$$1.length; i < len; i++) {
1092 var node = path$$1[i];
1093 var currentLeaf = [node.id, node.opts, []];
1094 if (leaf) {
1095 leaf[2].push(currentLeaf);
1096 leaf = currentLeaf;
1097 } else {
1098 root = leaf = currentLeaf;
1099 }
1100 }
1101 return root;
1102}
1103
1104// compare the IDs of two trees
1105function compareTree(a, b) {
1106 return a[0] < b[0] ? -1 : 1;
1107}
1108
1109// Merge two trees together
1110// The roots of tree1 and tree2 must be the same revision
1111function mergeTree(in_tree1, in_tree2) {
1112 var queue = [{tree1: in_tree1, tree2: in_tree2}];
1113 var conflicts = false;
1114 while (queue.length > 0) {
1115 var item = queue.pop();
1116 var tree1 = item.tree1;
1117 var tree2 = item.tree2;
1118
1119 if (tree1[1].status || tree2[1].status) {
1120 tree1[1].status =
1121 (tree1[1].status === 'available' ||
1122 tree2[1].status === 'available') ? 'available' : 'missing';
1123 }
1124
1125 for (var i = 0; i < tree2[2].length; i++) {
1126 if (!tree1[2][0]) {
1127 conflicts = 'new_leaf';
1128 tree1[2][0] = tree2[2][i];
1129 continue;
1130 }
1131
1132 var merged = false;
1133 for (var j = 0; j < tree1[2].length; j++) {
1134 if (tree1[2][j][0] === tree2[2][i][0]) {
1135 queue.push({tree1: tree1[2][j], tree2: tree2[2][i]});
1136 merged = true;
1137 }
1138 }
1139 if (!merged) {
1140 conflicts = 'new_branch';
1141 insertSorted(tree1[2], tree2[2][i], compareTree);
1142 }
1143 }
1144 }
1145 return {conflicts: conflicts, tree: in_tree1};
1146}
1147
1148function doMerge(tree, path$$1, dontExpand) {
1149 var restree = [];
1150 var conflicts = false;
1151 var merged = false;
1152 var res;
1153
1154 if (!tree.length) {
1155 return {tree: [path$$1], conflicts: 'new_leaf'};
1156 }
1157
1158 for (var i = 0, len = tree.length; i < len; i++) {
1159 var branch = tree[i];
1160 if (branch.pos === path$$1.pos && branch.ids[0] === path$$1.ids[0]) {
1161 // Paths start at the same position and have the same root, so they need
1162 // merged
1163 res = mergeTree(branch.ids, path$$1.ids);
1164 restree.push({pos: branch.pos, ids: res.tree});
1165 conflicts = conflicts || res.conflicts;
1166 merged = true;
1167 } else if (dontExpand !== true) {
1168 // The paths start at a different position, take the earliest path and
1169 // traverse up until it as at the same point from root as the path we
1170 // want to merge. If the keys match we return the longer path with the
1171 // other merged After stemming we dont want to expand the trees
1172
1173 var t1 = branch.pos < path$$1.pos ? branch : path$$1;
1174 var t2 = branch.pos < path$$1.pos ? path$$1 : branch;
1175 var diff = t2.pos - t1.pos;
1176
1177 var candidateParents = [];
1178
1179 var trees = [];
1180 trees.push({ids: t1.ids, diff: diff, parent: null, parentIdx: null});
1181 while (trees.length > 0) {
1182 var item = trees.pop();
1183 if (item.diff === 0) {
1184 if (item.ids[0] === t2.ids[0]) {
1185 candidateParents.push(item);
1186 }
1187 continue;
1188 }
1189 var elements = item.ids[2];
1190 for (var j = 0, elementsLen = elements.length; j < elementsLen; j++) {
1191 trees.push({
1192 ids: elements[j],
1193 diff: item.diff - 1,
1194 parent: item.ids,
1195 parentIdx: j
1196 });
1197 }
1198 }
1199
1200 var el = candidateParents[0];
1201
1202 if (!el) {
1203 restree.push(branch);
1204 } else {
1205 res = mergeTree(el.ids, t2.ids);
1206 el.parent[2][el.parentIdx] = res.tree;
1207 restree.push({pos: t1.pos, ids: t1.ids});
1208 conflicts = conflicts || res.conflicts;
1209 merged = true;
1210 }
1211 } else {
1212 restree.push(branch);
1213 }
1214 }
1215
1216 // We didnt find
1217 if (!merged) {
1218 restree.push(path$$1);
1219 }
1220
1221 restree.sort(sortByPos$1);
1222
1223 return {
1224 tree: restree,
1225 conflicts: conflicts || 'internal_node'
1226 };
1227}
1228
1229// To ensure we dont grow the revision tree infinitely, we stem old revisions
1230function stem(tree, depth) {
1231 // First we break out the tree into a complete list of root to leaf paths
1232 var paths = rootToLeaf(tree);
1233 var stemmedRevs;
1234
1235 var result;
1236 for (var i = 0, len = paths.length; i < len; i++) {
1237 // Then for each path, we cut off the start of the path based on the
1238 // `depth` to stem to, and generate a new set of flat trees
1239 var path$$1 = paths[i];
1240 var stemmed = path$$1.ids;
1241 var node;
1242 if (stemmed.length > depth) {
1243 // only do the stemming work if we actually need to stem
1244 if (!stemmedRevs) {
1245 stemmedRevs = {}; // avoid allocating this object unnecessarily
1246 }
1247 var numStemmed = stemmed.length - depth;
1248 node = {
1249 pos: path$$1.pos + numStemmed,
1250 ids: pathToTree(stemmed, numStemmed)
1251 };
1252
1253 for (var s = 0; s < numStemmed; s++) {
1254 var rev = (path$$1.pos + s) + '-' + stemmed[s].id;
1255 stemmedRevs[rev] = true;
1256 }
1257 } else { // no need to actually stem
1258 node = {
1259 pos: path$$1.pos,
1260 ids: pathToTree(stemmed, 0)
1261 };
1262 }
1263
1264 // Then we remerge all those flat trees together, ensuring that we dont
1265 // connect trees that would go beyond the depth limit
1266 if (result) {
1267 result = doMerge(result, node, true).tree;
1268 } else {
1269 result = [node];
1270 }
1271 }
1272
1273 // this is memory-heavy per Chrome profiler, avoid unless we actually stemmed
1274 if (stemmedRevs) {
1275 traverseRevTree(result, function (isLeaf, pos, revHash) {
1276 // some revisions may have been removed in a branch but not in another
1277 delete stemmedRevs[pos + '-' + revHash];
1278 });
1279 }
1280
1281 return {
1282 tree: result,
1283 revs: stemmedRevs ? Object.keys(stemmedRevs) : []
1284 };
1285}
1286
1287function merge(tree, path$$1, depth) {
1288 var newTree = doMerge(tree, path$$1);
1289 var stemmed = stem(newTree.tree, depth);
1290 return {
1291 tree: stemmed.tree,
1292 stemmedRevs: stemmed.revs,
1293 conflicts: newTree.conflicts
1294 };
1295}
1296
1297// return true if a rev exists in the rev tree, false otherwise
1298function revExists(revs, rev) {
1299 var toVisit = revs.slice();
1300 var splitRev = rev.split('-');
1301 var targetPos = parseInt(splitRev[0], 10);
1302 var targetId = splitRev[1];
1303
1304 var node;
1305 while ((node = toVisit.pop())) {
1306 if (node.pos === targetPos && node.ids[0] === targetId) {
1307 return true;
1308 }
1309 var branches = node.ids[2];
1310 for (var i = 0, len = branches.length; i < len; i++) {
1311 toVisit.push({pos: node.pos + 1, ids: branches[i]});
1312 }
1313 }
1314 return false;
1315}
1316
1317function getTrees(node) {
1318 return node.ids;
1319}
1320
1321// check if a specific revision of a doc has been deleted
1322// - metadata: the metadata object from the doc store
1323// - rev: (optional) the revision to check. defaults to winning revision
1324function isDeleted(metadata, rev) {
1325 if (!rev) {
1326 rev = winningRev(metadata);
1327 }
1328 var id = rev.substring(rev.indexOf('-') + 1);
1329 var toVisit = metadata.rev_tree.map(getTrees);
1330
1331 var tree;
1332 while ((tree = toVisit.pop())) {
1333 if (tree[0] === id) {
1334 return !!tree[1].deleted;
1335 }
1336 toVisit = toVisit.concat(tree[2]);
1337 }
1338}
1339
1340function isLocalId(id) {
1341 return (/^_local/).test(id);
1342}
1343
1344// returns the current leaf node for a given revision
1345function latest(rev, metadata) {
1346 var toVisit = metadata.rev_tree.slice();
1347 var node;
1348 while ((node = toVisit.pop())) {
1349 var pos = node.pos;
1350 var tree = node.ids;
1351 var id = tree[0];
1352 var opts = tree[1];
1353 var branches = tree[2];
1354 var isLeaf = branches.length === 0;
1355
1356 var history = node.history ? node.history.slice() : [];
1357 history.push({id: id, pos: pos, opts: opts});
1358
1359 if (isLeaf) {
1360 for (var i = 0, len = history.length; i < len; i++) {
1361 var historyNode = history[i];
1362 var historyRev = historyNode.pos + '-' + historyNode.id;
1363
1364 if (historyRev === rev) {
1365 // return the rev of this leaf
1366 return pos + '-' + id;
1367 }
1368 }
1369 }
1370
1371 for (var j = 0, l = branches.length; j < l; j++) {
1372 toVisit.push({pos: pos + 1, ids: branches[j], history: history});
1373 }
1374 }
1375
1376 /* istanbul ignore next */
1377 throw new Error('Unable to resolve latest revision for id ' + metadata.id + ', rev ' + rev);
1378}
1379
1380inherits(Changes$1, EventEmitter);
1381
1382function tryCatchInChangeListener(self, change, pending, lastSeq) {
1383 // isolate try/catches to avoid V8 deoptimizations
1384 try {
1385 self.emit('change', change, pending, lastSeq);
1386 } catch (e) {
1387 guardedConsole('error', 'Error in .on("change", function):', e);
1388 }
1389}
1390
1391function Changes$1(db, opts, callback) {
1392 EventEmitter.call(this);
1393 var self = this;
1394 this.db = db;
1395 opts = opts ? clone(opts) : {};
1396 var complete = opts.complete = once(function (err, resp) {
1397 if (err) {
1398 if (listenerCount(self, 'error') > 0) {
1399 self.emit('error', err);
1400 }
1401 } else {
1402 self.emit('complete', resp);
1403 }
1404 self.removeAllListeners();
1405 db.removeListener('destroyed', onDestroy);
1406 });
1407 if (callback) {
1408 self.on('complete', function (resp) {
1409 callback(null, resp);
1410 });
1411 self.on('error', callback);
1412 }
1413 function onDestroy() {
1414 self.cancel();
1415 }
1416 db.once('destroyed', onDestroy);
1417
1418 opts.onChange = function (change, pending, lastSeq) {
1419 /* istanbul ignore if */
1420 if (self.isCancelled) {
1421 return;
1422 }
1423 tryCatchInChangeListener(self, change, pending, lastSeq);
1424 };
1425
1426 var promise = new Promise(function (fulfill, reject) {
1427 opts.complete = function (err, res$$1) {
1428 if (err) {
1429 reject(err);
1430 } else {
1431 fulfill(res$$1);
1432 }
1433 };
1434 });
1435 self.once('cancel', function () {
1436 db.removeListener('destroyed', onDestroy);
1437 opts.complete(null, {status: 'cancelled'});
1438 });
1439 this.then = promise.then.bind(promise);
1440 this['catch'] = promise['catch'].bind(promise);
1441 this.then(function (result) {
1442 complete(null, result);
1443 }, complete);
1444
1445
1446
1447 if (!db.taskqueue.isReady) {
1448 db.taskqueue.addTask(function (failed) {
1449 if (failed) {
1450 opts.complete(failed);
1451 } else if (self.isCancelled) {
1452 self.emit('cancel');
1453 } else {
1454 self.validateChanges(opts);
1455 }
1456 });
1457 } else {
1458 self.validateChanges(opts);
1459 }
1460}
1461Changes$1.prototype.cancel = function () {
1462 this.isCancelled = true;
1463 if (this.db.taskqueue.isReady) {
1464 this.emit('cancel');
1465 }
1466};
1467function processChange(doc, metadata, opts) {
1468 var changeList = [{rev: doc._rev}];
1469 if (opts.style === 'all_docs') {
1470 changeList = collectLeaves(metadata.rev_tree)
1471 .map(function (x) { return {rev: x.rev}; });
1472 }
1473 var change = {
1474 id: metadata.id,
1475 changes: changeList,
1476 doc: doc
1477 };
1478
1479 if (isDeleted(metadata, doc._rev)) {
1480 change.deleted = true;
1481 }
1482 if (opts.conflicts) {
1483 change.doc._conflicts = collectConflicts(metadata);
1484 if (!change.doc._conflicts.length) {
1485 delete change.doc._conflicts;
1486 }
1487 }
1488 return change;
1489}
1490
1491Changes$1.prototype.validateChanges = function (opts) {
1492 var callback = opts.complete;
1493 var self = this;
1494
1495 /* istanbul ignore else */
1496 if (PouchDB._changesFilterPlugin) {
1497 PouchDB._changesFilterPlugin.validate(opts, function (err) {
1498 if (err) {
1499 return callback(err);
1500 }
1501 self.doChanges(opts);
1502 });
1503 } else {
1504 self.doChanges(opts);
1505 }
1506};
1507
1508Changes$1.prototype.doChanges = function (opts) {
1509 var self = this;
1510 var callback = opts.complete;
1511
1512 opts = clone(opts);
1513 if ('live' in opts && !('continuous' in opts)) {
1514 opts.continuous = opts.live;
1515 }
1516 opts.processChange = processChange;
1517
1518 if (opts.since === 'latest') {
1519 opts.since = 'now';
1520 }
1521 if (!opts.since) {
1522 opts.since = 0;
1523 }
1524 if (opts.since === 'now') {
1525 this.db.info().then(function (info) {
1526 /* istanbul ignore if */
1527 if (self.isCancelled) {
1528 callback(null, {status: 'cancelled'});
1529 return;
1530 }
1531 opts.since = info.update_seq;
1532 self.doChanges(opts);
1533 }, callback);
1534 return;
1535 }
1536
1537 /* istanbul ignore else */
1538 if (PouchDB._changesFilterPlugin) {
1539 PouchDB._changesFilterPlugin.normalize(opts);
1540 if (PouchDB._changesFilterPlugin.shouldFilter(this, opts)) {
1541 return PouchDB._changesFilterPlugin.filter(this, opts);
1542 }
1543 } else {
1544 ['doc_ids', 'filter', 'selector', 'view'].forEach(function (key) {
1545 if (key in opts) {
1546 guardedConsole('warn',
1547 'The "' + key + '" option was passed in to changes/replicate, ' +
1548 'but pouchdb-changes-filter plugin is not installed, so it ' +
1549 'was ignored. Please install the plugin to enable filtering.'
1550 );
1551 }
1552 });
1553 }
1554
1555 if (!('descending' in opts)) {
1556 opts.descending = false;
1557 }
1558
1559 // 0 and 1 should return 1 document
1560 opts.limit = opts.limit === 0 ? 1 : opts.limit;
1561 opts.complete = callback;
1562 var newPromise = this.db._changes(opts);
1563 /* istanbul ignore else */
1564 if (newPromise && typeof newPromise.cancel === 'function') {
1565 var cancel = self.cancel;
1566 self.cancel = getArguments(function (args) {
1567 newPromise.cancel();
1568 cancel.apply(this, args);
1569 });
1570 }
1571};
1572
1573/*
1574 * A generic pouch adapter
1575 */
1576
1577function compare(left, right) {
1578 return left < right ? -1 : left > right ? 1 : 0;
1579}
1580
1581// Wrapper for functions that call the bulkdocs api with a single doc,
1582// if the first result is an error, return an error
1583function yankError(callback, docId) {
1584 return function (err, results) {
1585 if (err || (results[0] && results[0].error)) {
1586 err = err || results[0];
1587 err.docId = docId;
1588 callback(err);
1589 } else {
1590 callback(null, results.length ? results[0] : results);
1591 }
1592 };
1593}
1594
1595// clean docs given to us by the user
1596function cleanDocs(docs) {
1597 for (var i = 0; i < docs.length; i++) {
1598 var doc = docs[i];
1599 if (doc._deleted) {
1600 delete doc._attachments; // ignore atts for deleted docs
1601 } else if (doc._attachments) {
1602 // filter out extraneous keys from _attachments
1603 var atts = Object.keys(doc._attachments);
1604 for (var j = 0; j < atts.length; j++) {
1605 var att = atts[j];
1606 doc._attachments[att] = pick(doc._attachments[att],
1607 ['data', 'digest', 'content_type', 'length', 'revpos', 'stub']);
1608 }
1609 }
1610 }
1611}
1612
1613// compare two docs, first by _id then by _rev
1614function compareByIdThenRev(a, b) {
1615 var idCompare = compare(a._id, b._id);
1616 if (idCompare !== 0) {
1617 return idCompare;
1618 }
1619 var aStart = a._revisions ? a._revisions.start : 0;
1620 var bStart = b._revisions ? b._revisions.start : 0;
1621 return compare(aStart, bStart);
1622}
1623
1624// for every node in a revision tree computes its distance from the closest
1625// leaf
1626function computeHeight(revs) {
1627 var height = {};
1628 var edges = [];
1629 traverseRevTree(revs, function (isLeaf, pos, id, prnt) {
1630 var rev = pos + "-" + id;
1631 if (isLeaf) {
1632 height[rev] = 0;
1633 }
1634 if (prnt !== undefined) {
1635 edges.push({from: prnt, to: rev});
1636 }
1637 return rev;
1638 });
1639
1640 edges.reverse();
1641 edges.forEach(function (edge) {
1642 if (height[edge.from] === undefined) {
1643 height[edge.from] = 1 + height[edge.to];
1644 } else {
1645 height[edge.from] = Math.min(height[edge.from], 1 + height[edge.to]);
1646 }
1647 });
1648 return height;
1649}
1650
1651function allDocsKeysParse(opts) {
1652 var keys = ('limit' in opts) ?
1653 opts.keys.slice(opts.skip, opts.limit + opts.skip) :
1654 (opts.skip > 0) ? opts.keys.slice(opts.skip) : opts.keys;
1655 opts.keys = keys;
1656 opts.skip = 0;
1657 delete opts.limit;
1658 if (opts.descending) {
1659 keys.reverse();
1660 opts.descending = false;
1661 }
1662}
1663
1664// all compaction is done in a queue, to avoid attaching
1665// too many listeners at once
1666function doNextCompaction(self) {
1667 var task = self._compactionQueue[0];
1668 var opts = task.opts;
1669 var callback = task.callback;
1670 self.get('_local/compaction').catch(function () {
1671 return false;
1672 }).then(function (doc) {
1673 if (doc && doc.last_seq) {
1674 opts.last_seq = doc.last_seq;
1675 }
1676 self._compact(opts, function (err, res$$1) {
1677 /* istanbul ignore if */
1678 if (err) {
1679 callback(err);
1680 } else {
1681 callback(null, res$$1);
1682 }
1683 nextTick(function () {
1684 self._compactionQueue.shift();
1685 if (self._compactionQueue.length) {
1686 doNextCompaction(self);
1687 }
1688 });
1689 });
1690 });
1691}
1692
1693function attachmentNameError(name) {
1694 if (name.charAt(0) === '_') {
1695 return name + ' is not a valid attachment name, attachment ' +
1696 'names cannot start with \'_\'';
1697 }
1698 return false;
1699}
1700
1701inherits(AbstractPouchDB, EventEmitter);
1702
1703function AbstractPouchDB() {
1704 EventEmitter.call(this);
1705
1706 // re-bind prototyped methods
1707 for (var p in AbstractPouchDB.prototype) {
1708 if (typeof this[p] === 'function') {
1709 this[p] = this[p].bind(this);
1710 }
1711 }
1712}
1713
1714AbstractPouchDB.prototype.post =
1715 adapterFun('post', function (doc, opts, callback) {
1716 if (typeof opts === 'function') {
1717 callback = opts;
1718 opts = {};
1719 }
1720 if (typeof doc !== 'object' || Array.isArray(doc)) {
1721 return callback(createError(NOT_AN_OBJECT));
1722 }
1723 this.bulkDocs({docs: [doc]}, opts, yankError(callback, doc._id));
1724});
1725
1726AbstractPouchDB.prototype.put = adapterFun('put', function (doc, opts, cb) {
1727 if (typeof opts === 'function') {
1728 cb = opts;
1729 opts = {};
1730 }
1731 if (typeof doc !== 'object' || Array.isArray(doc)) {
1732 return cb(createError(NOT_AN_OBJECT));
1733 }
1734 invalidIdError(doc._id);
1735 if (isLocalId(doc._id) && typeof this._putLocal === 'function') {
1736 if (doc._deleted) {
1737 return this._removeLocal(doc, cb);
1738 } else {
1739 return this._putLocal(doc, cb);
1740 }
1741 }
1742 var self = this;
1743 if (opts.force && doc._rev) {
1744 transformForceOptionToNewEditsOption();
1745 putDoc(function (err) {
1746 var result = err ? null : {ok: true, id: doc._id, rev: doc._rev};
1747 cb(err, result);
1748 });
1749 } else {
1750 putDoc(cb);
1751 }
1752
1753 function transformForceOptionToNewEditsOption() {
1754 var parts = doc._rev.split('-');
1755 var oldRevId = parts[1];
1756 var oldRevNum = parseInt(parts[0], 10);
1757
1758 var newRevNum = oldRevNum + 1;
1759 var newRevId = rev$$1();
1760
1761 doc._revisions = {
1762 start: newRevNum,
1763 ids: [newRevId, oldRevId]
1764 };
1765 doc._rev = newRevNum + '-' + newRevId;
1766 opts.new_edits = false;
1767 }
1768 function putDoc(next) {
1769 if (typeof self._put === 'function' && opts.new_edits !== false) {
1770 self._put(doc, opts, next);
1771 } else {
1772 self.bulkDocs({docs: [doc]}, opts, yankError(next, doc._id));
1773 }
1774 }
1775});
1776
1777AbstractPouchDB.prototype.putAttachment =
1778 adapterFun('putAttachment', function (docId, attachmentId, rev,
1779 blob, type) {
1780 var api = this;
1781 if (typeof type === 'function') {
1782 type = blob;
1783 blob = rev;
1784 rev = null;
1785 }
1786 // Lets fix in https://github.com/pouchdb/pouchdb/issues/3267
1787 /* istanbul ignore if */
1788 if (typeof type === 'undefined') {
1789 type = blob;
1790 blob = rev;
1791 rev = null;
1792 }
1793 if (!type) {
1794 guardedConsole('warn', 'Attachment', attachmentId, 'on document', docId, 'is missing content_type');
1795 }
1796
1797 function createAttachment(doc) {
1798 var prevrevpos = '_rev' in doc ? parseInt(doc._rev, 10) : 0;
1799 doc._attachments = doc._attachments || {};
1800 doc._attachments[attachmentId] = {
1801 content_type: type,
1802 data: blob,
1803 revpos: ++prevrevpos
1804 };
1805 return api.put(doc);
1806 }
1807
1808 return api.get(docId).then(function (doc) {
1809 if (doc._rev !== rev) {
1810 throw createError(REV_CONFLICT);
1811 }
1812
1813 return createAttachment(doc);
1814 }, function (err) {
1815 // create new doc
1816 /* istanbul ignore else */
1817 if (err.reason === MISSING_DOC.message) {
1818 return createAttachment({_id: docId});
1819 } else {
1820 throw err;
1821 }
1822 });
1823});
1824
1825AbstractPouchDB.prototype.removeAttachment =
1826 adapterFun('removeAttachment', function (docId, attachmentId, rev,
1827 callback) {
1828 var self = this;
1829 self.get(docId, function (err, obj$$1) {
1830 /* istanbul ignore if */
1831 if (err) {
1832 callback(err);
1833 return;
1834 }
1835 if (obj$$1._rev !== rev) {
1836 callback(createError(REV_CONFLICT));
1837 return;
1838 }
1839 /* istanbul ignore if */
1840 if (!obj$$1._attachments) {
1841 return callback();
1842 }
1843 delete obj$$1._attachments[attachmentId];
1844 if (Object.keys(obj$$1._attachments).length === 0) {
1845 delete obj$$1._attachments;
1846 }
1847 self.put(obj$$1, callback);
1848 });
1849});
1850
1851AbstractPouchDB.prototype.remove =
1852 adapterFun('remove', function (docOrId, optsOrRev, opts, callback) {
1853 var doc;
1854 if (typeof optsOrRev === 'string') {
1855 // id, rev, opts, callback style
1856 doc = {
1857 _id: docOrId,
1858 _rev: optsOrRev
1859 };
1860 if (typeof opts === 'function') {
1861 callback = opts;
1862 opts = {};
1863 }
1864 } else {
1865 // doc, opts, callback style
1866 doc = docOrId;
1867 if (typeof optsOrRev === 'function') {
1868 callback = optsOrRev;
1869 opts = {};
1870 } else {
1871 callback = opts;
1872 opts = optsOrRev;
1873 }
1874 }
1875 opts = opts || {};
1876 opts.was_delete = true;
1877 var newDoc = {_id: doc._id, _rev: (doc._rev || opts.rev)};
1878 newDoc._deleted = true;
1879 if (isLocalId(newDoc._id) && typeof this._removeLocal === 'function') {
1880 return this._removeLocal(doc, callback);
1881 }
1882 this.bulkDocs({docs: [newDoc]}, opts, yankError(callback, newDoc._id));
1883});
1884
1885AbstractPouchDB.prototype.revsDiff =
1886 adapterFun('revsDiff', function (req, opts, callback) {
1887 if (typeof opts === 'function') {
1888 callback = opts;
1889 opts = {};
1890 }
1891 var ids = Object.keys(req);
1892
1893 if (!ids.length) {
1894 return callback(null, {});
1895 }
1896
1897 var count = 0;
1898 var missing = new ExportedMap();
1899
1900 function addToMissing(id, revId) {
1901 if (!missing.has(id)) {
1902 missing.set(id, {missing: []});
1903 }
1904 missing.get(id).missing.push(revId);
1905 }
1906
1907 function processDoc(id, rev_tree) {
1908 // Is this fast enough? Maybe we should switch to a set simulated by a map
1909 var missingForId = req[id].slice(0);
1910 traverseRevTree(rev_tree, function (isLeaf, pos, revHash, ctx,
1911 opts) {
1912 var rev = pos + '-' + revHash;
1913 var idx = missingForId.indexOf(rev);
1914 if (idx === -1) {
1915 return;
1916 }
1917
1918 missingForId.splice(idx, 1);
1919 /* istanbul ignore if */
1920 if (opts.status !== 'available') {
1921 addToMissing(id, rev);
1922 }
1923 });
1924
1925 // Traversing the tree is synchronous, so now `missingForId` contains
1926 // revisions that were not found in the tree
1927 missingForId.forEach(function (rev) {
1928 addToMissing(id, rev);
1929 });
1930 }
1931
1932 ids.map(function (id) {
1933 this._getRevisionTree(id, function (err, rev_tree) {
1934 if (err && err.status === 404 && err.message === 'missing') {
1935 missing.set(id, {missing: req[id]});
1936 } else if (err) {
1937 /* istanbul ignore next */
1938 return callback(err);
1939 } else {
1940 processDoc(id, rev_tree);
1941 }
1942
1943 if (++count === ids.length) {
1944 // convert LazyMap to object
1945 var missingObj = {};
1946 missing.forEach(function (value, key) {
1947 missingObj[key] = value;
1948 });
1949 return callback(null, missingObj);
1950 }
1951 });
1952 }, this);
1953});
1954
1955// _bulk_get API for faster replication, as described in
1956// https://github.com/apache/couchdb-chttpd/pull/33
1957// At the "abstract" level, it will just run multiple get()s in
1958// parallel, because this isn't much of a performance cost
1959// for local databases (except the cost of multiple transactions, which is
1960// small). The http adapter overrides this in order
1961// to do a more efficient single HTTP request.
1962AbstractPouchDB.prototype.bulkGet =
1963 adapterFun('bulkGet', function (opts, callback) {
1964 bulkGet(this, opts, callback);
1965});
1966
1967// compact one document and fire callback
1968// by compacting we mean removing all revisions which
1969// are further from the leaf in revision tree than max_height
1970AbstractPouchDB.prototype.compactDocument =
1971 adapterFun('compactDocument', function (docId, maxHeight, callback) {
1972 var self = this;
1973 this._getRevisionTree(docId, function (err, revTree) {
1974 /* istanbul ignore if */
1975 if (err) {
1976 return callback(err);
1977 }
1978 var height = computeHeight(revTree);
1979 var candidates = [];
1980 var revs = [];
1981 Object.keys(height).forEach(function (rev) {
1982 if (height[rev] > maxHeight) {
1983 candidates.push(rev);
1984 }
1985 });
1986
1987 traverseRevTree(revTree, function (isLeaf, pos, revHash, ctx, opts) {
1988 var rev = pos + '-' + revHash;
1989 if (opts.status === 'available' && candidates.indexOf(rev) !== -1) {
1990 revs.push(rev);
1991 }
1992 });
1993 self._doCompaction(docId, revs, callback);
1994 });
1995});
1996
1997// compact the whole database using single document
1998// compaction
1999AbstractPouchDB.prototype.compact =
2000 adapterFun('compact', function (opts, callback) {
2001 if (typeof opts === 'function') {
2002 callback = opts;
2003 opts = {};
2004 }
2005
2006 var self = this;
2007 opts = opts || {};
2008
2009 self._compactionQueue = self._compactionQueue || [];
2010 self._compactionQueue.push({opts: opts, callback: callback});
2011 if (self._compactionQueue.length === 1) {
2012 doNextCompaction(self);
2013 }
2014});
2015AbstractPouchDB.prototype._compact = function (opts, callback) {
2016 var self = this;
2017 var changesOpts = {
2018 return_docs: false,
2019 last_seq: opts.last_seq || 0
2020 };
2021 var promises = [];
2022
2023 function onChange(row) {
2024 promises.push(self.compactDocument(row.id, 0));
2025 }
2026 function onComplete(resp) {
2027 var lastSeq = resp.last_seq;
2028 Promise.all(promises).then(function () {
2029 return upsert(self, '_local/compaction', function deltaFunc(doc) {
2030 if (!doc.last_seq || doc.last_seq < lastSeq) {
2031 doc.last_seq = lastSeq;
2032 return doc;
2033 }
2034 return false; // somebody else got here first, don't update
2035 });
2036 }).then(function () {
2037 callback(null, {ok: true});
2038 }).catch(callback);
2039 }
2040 self.changes(changesOpts)
2041 .on('change', onChange)
2042 .on('complete', onComplete)
2043 .on('error', callback);
2044};
2045
2046/* Begin api wrappers. Specific functionality to storage belongs in the
2047 _[method] */
2048AbstractPouchDB.prototype.get = adapterFun('get', function (id, opts, cb) {
2049 if (typeof opts === 'function') {
2050 cb = opts;
2051 opts = {};
2052 }
2053 if (typeof id !== 'string') {
2054 return cb(createError(INVALID_ID));
2055 }
2056 if (isLocalId(id) && typeof this._getLocal === 'function') {
2057 return this._getLocal(id, cb);
2058 }
2059 var leaves = [], self = this;
2060
2061 function finishOpenRevs() {
2062 var result = [];
2063 var count = leaves.length;
2064 /* istanbul ignore if */
2065 if (!count) {
2066 return cb(null, result);
2067 }
2068
2069 // order with open_revs is unspecified
2070 leaves.forEach(function (leaf) {
2071 self.get(id, {
2072 rev: leaf,
2073 revs: opts.revs,
2074 latest: opts.latest,
2075 attachments: opts.attachments,
2076 binary: opts.binary
2077 }, function (err, doc) {
2078 if (!err) {
2079 // using latest=true can produce duplicates
2080 var existing;
2081 for (var i = 0, l = result.length; i < l; i++) {
2082 if (result[i].ok && result[i].ok._rev === doc._rev) {
2083 existing = true;
2084 break;
2085 }
2086 }
2087 if (!existing) {
2088 result.push({ok: doc});
2089 }
2090 } else {
2091 result.push({missing: leaf});
2092 }
2093 count--;
2094 if (!count) {
2095 cb(null, result);
2096 }
2097 });
2098 });
2099 }
2100
2101 if (opts.open_revs) {
2102 if (opts.open_revs === "all") {
2103 this._getRevisionTree(id, function (err, rev_tree) {
2104 /* istanbul ignore if */
2105 if (err) {
2106 return cb(err);
2107 }
2108 leaves = collectLeaves(rev_tree).map(function (leaf) {
2109 return leaf.rev;
2110 });
2111 finishOpenRevs();
2112 });
2113 } else {
2114 if (Array.isArray(opts.open_revs)) {
2115 leaves = opts.open_revs;
2116 for (var i = 0; i < leaves.length; i++) {
2117 var l = leaves[i];
2118 // looks like it's the only thing couchdb checks
2119 if (!(typeof (l) === "string" && /^\d+-/.test(l))) {
2120 return cb(createError(INVALID_REV));
2121 }
2122 }
2123 finishOpenRevs();
2124 } else {
2125 return cb(createError(UNKNOWN_ERROR, 'function_clause'));
2126 }
2127 }
2128 return; // open_revs does not like other options
2129 }
2130
2131 return this._get(id, opts, function (err, result) {
2132 if (err) {
2133 err.docId = id;
2134 return cb(err);
2135 }
2136
2137 var doc = result.doc;
2138 var metadata = result.metadata;
2139 var ctx = result.ctx;
2140
2141 if (opts.conflicts) {
2142 var conflicts = collectConflicts(metadata);
2143 if (conflicts.length) {
2144 doc._conflicts = conflicts;
2145 }
2146 }
2147
2148 if (isDeleted(metadata, doc._rev)) {
2149 doc._deleted = true;
2150 }
2151
2152 if (opts.revs || opts.revs_info) {
2153 var splittedRev = doc._rev.split('-');
2154 var revNo = parseInt(splittedRev[0], 10);
2155 var revHash = splittedRev[1];
2156
2157 var paths = rootToLeaf(metadata.rev_tree);
2158 var path$$1 = null;
2159
2160 for (var i = 0; i < paths.length; i++) {
2161 var currentPath = paths[i];
2162 var hashIndex = currentPath.ids.map(function (x) { return x.id; })
2163 .indexOf(revHash);
2164 var hashFoundAtRevPos = hashIndex === (revNo - 1);
2165
2166 if (hashFoundAtRevPos || (!path$$1 && hashIndex !== -1)) {
2167 path$$1 = currentPath;
2168 }
2169 }
2170
2171 var indexOfRev = path$$1.ids.map(function (x) { return x.id; })
2172 .indexOf(doc._rev.split('-')[1]) + 1;
2173 var howMany = path$$1.ids.length - indexOfRev;
2174 path$$1.ids.splice(indexOfRev, howMany);
2175 path$$1.ids.reverse();
2176
2177 if (opts.revs) {
2178 doc._revisions = {
2179 start: (path$$1.pos + path$$1.ids.length) - 1,
2180 ids: path$$1.ids.map(function (rev) {
2181 return rev.id;
2182 })
2183 };
2184 }
2185 if (opts.revs_info) {
2186 var pos = path$$1.pos + path$$1.ids.length;
2187 doc._revs_info = path$$1.ids.map(function (rev) {
2188 pos--;
2189 return {
2190 rev: pos + '-' + rev.id,
2191 status: rev.opts.status
2192 };
2193 });
2194 }
2195 }
2196
2197 if (opts.attachments && doc._attachments) {
2198 var attachments = doc._attachments;
2199 var count = Object.keys(attachments).length;
2200 if (count === 0) {
2201 return cb(null, doc);
2202 }
2203 Object.keys(attachments).forEach(function (key) {
2204 this._getAttachment(doc._id, key, attachments[key], {
2205 // Previously the revision handling was done in adapter.js
2206 // getAttachment, however since idb-next doesnt we need to
2207 // pass the rev through
2208 rev: doc._rev,
2209 binary: opts.binary,
2210 ctx: ctx
2211 }, function (err, data) {
2212 var att = doc._attachments[key];
2213 att.data = data;
2214 delete att.stub;
2215 delete att.length;
2216 if (!--count) {
2217 cb(null, doc);
2218 }
2219 });
2220 }, self);
2221 } else {
2222 if (doc._attachments) {
2223 for (var key in doc._attachments) {
2224 /* istanbul ignore else */
2225 if (doc._attachments.hasOwnProperty(key)) {
2226 doc._attachments[key].stub = true;
2227 }
2228 }
2229 }
2230 cb(null, doc);
2231 }
2232 });
2233});
2234
2235// TODO: I dont like this, it forces an extra read for every
2236// attachment read and enforces a confusing api between
2237// adapter.js and the adapter implementation
2238AbstractPouchDB.prototype.getAttachment =
2239 adapterFun('getAttachment', function (docId, attachmentId, opts, callback) {
2240 var self = this;
2241 if (opts instanceof Function) {
2242 callback = opts;
2243 opts = {};
2244 }
2245 this._get(docId, opts, function (err, res$$1) {
2246 if (err) {
2247 return callback(err);
2248 }
2249 if (res$$1.doc._attachments && res$$1.doc._attachments[attachmentId]) {
2250 opts.ctx = res$$1.ctx;
2251 opts.binary = true;
2252 self._getAttachment(docId, attachmentId,
2253 res$$1.doc._attachments[attachmentId], opts, callback);
2254 } else {
2255 return callback(createError(MISSING_DOC));
2256 }
2257 });
2258});
2259
2260AbstractPouchDB.prototype.allDocs =
2261 adapterFun('allDocs', function (opts, callback) {
2262 if (typeof opts === 'function') {
2263 callback = opts;
2264 opts = {};
2265 }
2266 opts.skip = typeof opts.skip !== 'undefined' ? opts.skip : 0;
2267 if (opts.start_key) {
2268 opts.startkey = opts.start_key;
2269 }
2270 if (opts.end_key) {
2271 opts.endkey = opts.end_key;
2272 }
2273 if ('keys' in opts) {
2274 if (!Array.isArray(opts.keys)) {
2275 return callback(new TypeError('options.keys must be an array'));
2276 }
2277 var incompatibleOpt =
2278 ['startkey', 'endkey', 'key'].filter(function (incompatibleOpt) {
2279 return incompatibleOpt in opts;
2280 })[0];
2281 if (incompatibleOpt) {
2282 callback(createError(QUERY_PARSE_ERROR,
2283 'Query parameter `' + incompatibleOpt +
2284 '` is not compatible with multi-get'
2285 ));
2286 return;
2287 }
2288 if (!isRemote(this)) {
2289 allDocsKeysParse(opts);
2290 if (opts.keys.length === 0) {
2291 return this._allDocs({limit: 0}, callback);
2292 }
2293 }
2294 }
2295
2296 return this._allDocs(opts, callback);
2297});
2298
2299AbstractPouchDB.prototype.changes = function (opts, callback) {
2300 if (typeof opts === 'function') {
2301 callback = opts;
2302 opts = {};
2303 }
2304
2305 opts = opts || {};
2306
2307 // By default set return_docs to false if the caller has opts.live = true,
2308 // this will prevent us from collecting the set of changes indefinitely
2309 // resulting in growing memory
2310 opts.return_docs = ('return_docs' in opts) ? opts.return_docs : !opts.live;
2311
2312 return new Changes$1(this, opts, callback);
2313};
2314
2315AbstractPouchDB.prototype.close = adapterFun('close', function (callback) {
2316 this._closed = true;
2317 this.emit('closed');
2318 return this._close(callback);
2319});
2320
2321AbstractPouchDB.prototype.info = adapterFun('info', function (callback) {
2322 var self = this;
2323 this._info(function (err, info) {
2324 if (err) {
2325 return callback(err);
2326 }
2327 // assume we know better than the adapter, unless it informs us
2328 info.db_name = info.db_name || self.name;
2329 info.auto_compaction = !!(self.auto_compaction && !isRemote(self));
2330 info.adapter = self.adapter;
2331 callback(null, info);
2332 });
2333});
2334
2335AbstractPouchDB.prototype.id = adapterFun('id', function (callback) {
2336 return this._id(callback);
2337});
2338
2339/* istanbul ignore next */
2340AbstractPouchDB.prototype.type = function () {
2341 return (typeof this._type === 'function') ? this._type() : this.adapter;
2342};
2343
2344AbstractPouchDB.prototype.bulkDocs =
2345 adapterFun('bulkDocs', function (req, opts, callback) {
2346 if (typeof opts === 'function') {
2347 callback = opts;
2348 opts = {};
2349 }
2350
2351 opts = opts || {};
2352
2353 if (Array.isArray(req)) {
2354 req = {
2355 docs: req
2356 };
2357 }
2358
2359 if (!req || !req.docs || !Array.isArray(req.docs)) {
2360 return callback(createError(MISSING_BULK_DOCS));
2361 }
2362
2363 for (var i = 0; i < req.docs.length; ++i) {
2364 if (typeof req.docs[i] !== 'object' || Array.isArray(req.docs[i])) {
2365 return callback(createError(NOT_AN_OBJECT));
2366 }
2367 }
2368
2369 var attachmentError;
2370 req.docs.forEach(function (doc) {
2371 if (doc._attachments) {
2372 Object.keys(doc._attachments).forEach(function (name) {
2373 attachmentError = attachmentError || attachmentNameError(name);
2374 if (!doc._attachments[name].content_type) {
2375 guardedConsole('warn', 'Attachment', name, 'on document', doc._id, 'is missing content_type');
2376 }
2377 });
2378 }
2379 });
2380
2381 if (attachmentError) {
2382 return callback(createError(BAD_REQUEST, attachmentError));
2383 }
2384
2385 if (!('new_edits' in opts)) {
2386 if ('new_edits' in req) {
2387 opts.new_edits = req.new_edits;
2388 } else {
2389 opts.new_edits = true;
2390 }
2391 }
2392
2393 var adapter = this;
2394 if (!opts.new_edits && !isRemote(adapter)) {
2395 // ensure revisions of the same doc are sorted, so that
2396 // the local adapter processes them correctly (#2935)
2397 req.docs.sort(compareByIdThenRev);
2398 }
2399
2400 cleanDocs(req.docs);
2401
2402 // in the case of conflicts, we want to return the _ids to the user
2403 // however, the underlying adapter may destroy the docs array, so
2404 // create a copy here
2405 var ids = req.docs.map(function (doc) {
2406 return doc._id;
2407 });
2408
2409 return this._bulkDocs(req, opts, function (err, res$$1) {
2410 if (err) {
2411 return callback(err);
2412 }
2413 if (!opts.new_edits) {
2414 // this is what couch does when new_edits is false
2415 res$$1 = res$$1.filter(function (x) {
2416 return x.error;
2417 });
2418 }
2419 // add ids for error/conflict responses (not required for CouchDB)
2420 if (!isRemote(adapter)) {
2421 for (var i = 0, l = res$$1.length; i < l; i++) {
2422 res$$1[i].id = res$$1[i].id || ids[i];
2423 }
2424 }
2425
2426 callback(null, res$$1);
2427 });
2428});
2429
2430AbstractPouchDB.prototype.registerDependentDatabase =
2431 adapterFun('registerDependentDatabase', function (dependentDb,
2432 callback) {
2433 var depDB = new this.constructor(dependentDb, this.__opts);
2434
2435 function diffFun(doc) {
2436 doc.dependentDbs = doc.dependentDbs || {};
2437 if (doc.dependentDbs[dependentDb]) {
2438 return false; // no update required
2439 }
2440 doc.dependentDbs[dependentDb] = true;
2441 return doc;
2442 }
2443 upsert(this, '_local/_pouch_dependentDbs', diffFun)
2444 .then(function () {
2445 callback(null, {db: depDB});
2446 }).catch(callback);
2447});
2448
2449AbstractPouchDB.prototype.destroy =
2450 adapterFun('destroy', function (opts, callback) {
2451
2452 if (typeof opts === 'function') {
2453 callback = opts;
2454 opts = {};
2455 }
2456
2457 var self = this;
2458 var usePrefix = 'use_prefix' in self ? self.use_prefix : true;
2459
2460 function destroyDb() {
2461 // call destroy method of the particular adaptor
2462 self._destroy(opts, function (err, resp) {
2463 if (err) {
2464 return callback(err);
2465 }
2466 self._destroyed = true;
2467 self.emit('destroyed');
2468 callback(null, resp || { 'ok': true });
2469 });
2470 }
2471
2472 if (isRemote(self)) {
2473 // no need to check for dependent DBs if it's a remote DB
2474 return destroyDb();
2475 }
2476
2477 self.get('_local/_pouch_dependentDbs', function (err, localDoc) {
2478 if (err) {
2479 /* istanbul ignore if */
2480 if (err.status !== 404) {
2481 return callback(err);
2482 } else { // no dependencies
2483 return destroyDb();
2484 }
2485 }
2486 var dependentDbs = localDoc.dependentDbs;
2487 var PouchDB = self.constructor;
2488 var deletedMap = Object.keys(dependentDbs).map(function (name) {
2489 // use_prefix is only false in the browser
2490 /* istanbul ignore next */
2491 var trueName = usePrefix ?
2492 name.replace(new RegExp('^' + PouchDB.prefix), '') : name;
2493 return new PouchDB(trueName, self.__opts).destroy();
2494 });
2495 Promise.all(deletedMap).then(destroyDb, callback);
2496 });
2497});
2498
2499function TaskQueue() {
2500 this.isReady = false;
2501 this.failed = false;
2502 this.queue = [];
2503}
2504
2505TaskQueue.prototype.execute = function () {
2506 var fun;
2507 if (this.failed) {
2508 while ((fun = this.queue.shift())) {
2509 fun(this.failed);
2510 }
2511 } else {
2512 while ((fun = this.queue.shift())) {
2513 fun();
2514 }
2515 }
2516};
2517
2518TaskQueue.prototype.fail = function (err) {
2519 this.failed = err;
2520 this.execute();
2521};
2522
2523TaskQueue.prototype.ready = function (db) {
2524 this.isReady = true;
2525 this.db = db;
2526 this.execute();
2527};
2528
2529TaskQueue.prototype.addTask = function (fun) {
2530 this.queue.push(fun);
2531 if (this.failed) {
2532 this.execute();
2533 }
2534};
2535
2536function parseAdapter(name, opts) {
2537 var match = name.match(/([a-z-]*):\/\/(.*)/);
2538 if (match) {
2539 // the http adapter expects the fully qualified name
2540 return {
2541 name: /https?/.test(match[1]) ? match[1] + '://' + match[2] : match[2],
2542 adapter: match[1]
2543 };
2544 }
2545
2546 var adapters = PouchDB.adapters;
2547 var preferredAdapters = PouchDB.preferredAdapters;
2548 var prefix = PouchDB.prefix;
2549 var adapterName = opts.adapter;
2550
2551 if (!adapterName) { // automatically determine adapter
2552 for (var i = 0; i < preferredAdapters.length; ++i) {
2553 adapterName = preferredAdapters[i];
2554 // check for browsers that have been upgraded from websql-only to websql+idb
2555 /* istanbul ignore if */
2556 if (adapterName === 'idb' && 'websql' in adapters &&
2557 hasLocalStorage() && localStorage['_pouch__websqldb_' + prefix + name]) {
2558 // log it, because this can be confusing during development
2559 guardedConsole('log', 'PouchDB is downgrading "' + name + '" to WebSQL to' +
2560 ' avoid data loss, because it was already opened with WebSQL.');
2561 continue; // keep using websql to avoid user data loss
2562 }
2563 break;
2564 }
2565 }
2566
2567 var adapter = adapters[adapterName];
2568
2569 // if adapter is invalid, then an error will be thrown later
2570 var usePrefix = (adapter && 'use_prefix' in adapter) ?
2571 adapter.use_prefix : true;
2572
2573 return {
2574 name: usePrefix ? (prefix + name) : name,
2575 adapter: adapterName
2576 };
2577}
2578
2579// OK, so here's the deal. Consider this code:
2580// var db1 = new PouchDB('foo');
2581// var db2 = new PouchDB('foo');
2582// db1.destroy();
2583// ^ these two both need to emit 'destroyed' events,
2584// as well as the PouchDB constructor itself.
2585// So we have one db object (whichever one got destroy() called on it)
2586// responsible for emitting the initial event, which then gets emitted
2587// by the constructor, which then broadcasts it to any other dbs
2588// that may have been created with the same name.
2589function prepareForDestruction(self) {
2590
2591 function onDestroyed(from_constructor) {
2592 self.removeListener('closed', onClosed);
2593 if (!from_constructor) {
2594 self.constructor.emit('destroyed', self.name);
2595 }
2596 }
2597
2598 function onClosed() {
2599 self.removeListener('destroyed', onDestroyed);
2600 self.constructor.emit('unref', self);
2601 }
2602
2603 self.once('destroyed', onDestroyed);
2604 self.once('closed', onClosed);
2605 self.constructor.emit('ref', self);
2606}
2607
2608inherits(PouchDB, AbstractPouchDB);
2609function PouchDB(name, opts) {
2610 // In Node our test suite only tests this for PouchAlt unfortunately
2611 /* istanbul ignore if */
2612 if (!(this instanceof PouchDB)) {
2613 return new PouchDB(name, opts);
2614 }
2615
2616 var self = this;
2617 opts = opts || {};
2618
2619 if (name && typeof name === 'object') {
2620 opts = name;
2621 name = opts.name;
2622 delete opts.name;
2623 }
2624
2625 if (opts.deterministic_revs === undefined) {
2626 opts.deterministic_revs = true;
2627 }
2628
2629 this.__opts = opts = clone(opts);
2630
2631 self.auto_compaction = opts.auto_compaction;
2632 self.prefix = PouchDB.prefix;
2633
2634 if (typeof name !== 'string') {
2635 throw new Error('Missing/invalid DB name');
2636 }
2637
2638 var prefixedName = (opts.prefix || '') + name;
2639 var backend = parseAdapter(prefixedName, opts);
2640
2641 opts.name = backend.name;
2642 opts.adapter = opts.adapter || backend.adapter;
2643
2644 self.name = name;
2645 self._adapter = opts.adapter;
2646 PouchDB.emit('debug', ['adapter', 'Picked adapter: ', opts.adapter]);
2647
2648 if (!PouchDB.adapters[opts.adapter] ||
2649 !PouchDB.adapters[opts.adapter].valid()) {
2650 throw new Error('Invalid Adapter: ' + opts.adapter);
2651 }
2652
2653 AbstractPouchDB.call(self);
2654 self.taskqueue = new TaskQueue();
2655
2656 self.adapter = opts.adapter;
2657
2658 PouchDB.adapters[opts.adapter].call(self, opts, function (err) {
2659 if (err) {
2660 return self.taskqueue.fail(err);
2661 }
2662 prepareForDestruction(self);
2663
2664 self.emit('created', self);
2665 PouchDB.emit('created', self.name);
2666 self.taskqueue.ready(self);
2667 });
2668
2669}
2670
2671var fetch = fetchCookie(nodeFetch);
2672
2673/* We can fake the abort, the http adapter keeps track
2674 of ignoring the result */
2675function AbortController() {
2676 return {abort: function () {}};
2677}
2678
2679var Headers = nodeFetch.Headers;
2680
2681PouchDB.adapters = {};
2682PouchDB.preferredAdapters = [];
2683
2684PouchDB.prefix = '_pouch_';
2685
2686var eventEmitter = new EventEmitter();
2687
2688function setUpEventEmitter(Pouch) {
2689 Object.keys(EventEmitter.prototype).forEach(function (key) {
2690 if (typeof EventEmitter.prototype[key] === 'function') {
2691 Pouch[key] = eventEmitter[key].bind(eventEmitter);
2692 }
2693 });
2694
2695 // these are created in constructor.js, and allow us to notify each DB with
2696 // the same name that it was destroyed, via the constructor object
2697 var destructListeners = Pouch._destructionListeners = new ExportedMap();
2698
2699 Pouch.on('ref', function onConstructorRef(db) {
2700 if (!destructListeners.has(db.name)) {
2701 destructListeners.set(db.name, []);
2702 }
2703 destructListeners.get(db.name).push(db);
2704 });
2705
2706 Pouch.on('unref', function onConstructorUnref(db) {
2707 if (!destructListeners.has(db.name)) {
2708 return;
2709 }
2710 var dbList = destructListeners.get(db.name);
2711 var pos = dbList.indexOf(db);
2712 if (pos < 0) {
2713 /* istanbul ignore next */
2714 return;
2715 }
2716 dbList.splice(pos, 1);
2717 if (dbList.length > 1) {
2718 /* istanbul ignore next */
2719 destructListeners.set(db.name, dbList);
2720 } else {
2721 destructListeners.delete(db.name);
2722 }
2723 });
2724
2725 Pouch.on('destroyed', function onConstructorDestroyed(name) {
2726 if (!destructListeners.has(name)) {
2727 return;
2728 }
2729 var dbList = destructListeners.get(name);
2730 destructListeners.delete(name);
2731 dbList.forEach(function (db) {
2732 db.emit('destroyed',true);
2733 });
2734 });
2735}
2736
2737setUpEventEmitter(PouchDB);
2738
2739PouchDB.adapter = function (id, obj$$1, addToPreferredAdapters) {
2740 /* istanbul ignore else */
2741 if (obj$$1.valid()) {
2742 PouchDB.adapters[id] = obj$$1;
2743 if (addToPreferredAdapters) {
2744 PouchDB.preferredAdapters.push(id);
2745 }
2746 }
2747};
2748
2749PouchDB.plugin = function (obj$$1) {
2750 if (typeof obj$$1 === 'function') { // function style for plugins
2751 obj$$1(PouchDB);
2752 } else if (typeof obj$$1 !== 'object' || Object.keys(obj$$1).length === 0) {
2753 throw new Error('Invalid plugin: got "' + obj$$1 + '", expected an object or a function');
2754 } else {
2755 Object.keys(obj$$1).forEach(function (id) { // object style for plugins
2756 PouchDB.prototype[id] = obj$$1[id];
2757 });
2758 }
2759 if (this.__defaults) {
2760 PouchDB.__defaults = $inject_Object_assign({}, this.__defaults);
2761 }
2762 return PouchDB;
2763};
2764
2765PouchDB.defaults = function (defaultOpts) {
2766 function PouchAlt(name, opts) {
2767 if (!(this instanceof PouchAlt)) {
2768 return new PouchAlt(name, opts);
2769 }
2770
2771 opts = opts || {};
2772
2773 if (name && typeof name === 'object') {
2774 opts = name;
2775 name = opts.name;
2776 delete opts.name;
2777 }
2778
2779 opts = $inject_Object_assign({}, PouchAlt.__defaults, opts);
2780 PouchDB.call(this, name, opts);
2781 }
2782
2783 inherits(PouchAlt, PouchDB);
2784
2785 PouchAlt.preferredAdapters = PouchDB.preferredAdapters.slice();
2786 Object.keys(PouchDB).forEach(function (key) {
2787 if (!(key in PouchAlt)) {
2788 PouchAlt[key] = PouchDB[key];
2789 }
2790 });
2791
2792 // make default options transitive
2793 // https://github.com/pouchdb/pouchdb/issues/5922
2794 PouchAlt.__defaults = $inject_Object_assign({}, this.__defaults, defaultOpts);
2795
2796 return PouchAlt;
2797};
2798
2799PouchDB.fetch = function (url, opts) {
2800 return fetch(url, opts);
2801};
2802
2803// managed automatically by set-version.js
2804var version = "7.0.0";
2805
2806// this would just be "return doc[field]", but fields
2807// can be "deep" due to dot notation
2808function getFieldFromDoc(doc, parsedField) {
2809 var value = doc;
2810 for (var i = 0, len = parsedField.length; i < len; i++) {
2811 var key = parsedField[i];
2812 value = value[key];
2813 if (!value) {
2814 break;
2815 }
2816 }
2817 return value;
2818}
2819
2820function compare$1(left, right) {
2821 return left < right ? -1 : left > right ? 1 : 0;
2822}
2823
2824// Converts a string in dot notation to an array of its components, with backslash escaping
2825function parseField(fieldName) {
2826 // fields may be deep (e.g. "foo.bar.baz"), so parse
2827 var fields = [];
2828 var current = '';
2829 for (var i = 0, len = fieldName.length; i < len; i++) {
2830 var ch = fieldName[i];
2831 if (ch === '.') {
2832 if (i > 0 && fieldName[i - 1] === '\\') { // escaped delimiter
2833 current = current.substring(0, current.length - 1) + '.';
2834 } else { // not escaped, so delimiter
2835 fields.push(current);
2836 current = '';
2837 }
2838 } else { // normal character
2839 current += ch;
2840 }
2841 }
2842 fields.push(current);
2843 return fields;
2844}
2845
2846var combinationFields = ['$or', '$nor', '$not'];
2847function isCombinationalField(field) {
2848 return combinationFields.indexOf(field) > -1;
2849}
2850
2851function getKey(obj$$1) {
2852 return Object.keys(obj$$1)[0];
2853}
2854
2855function getValue(obj$$1) {
2856 return obj$$1[getKey(obj$$1)];
2857}
2858
2859
2860// flatten an array of selectors joined by an $and operator
2861function mergeAndedSelectors(selectors) {
2862
2863 // sort to ensure that e.g. if the user specified
2864 // $and: [{$gt: 'a'}, {$gt: 'b'}], then it's collapsed into
2865 // just {$gt: 'b'}
2866 var res$$1 = {};
2867
2868 selectors.forEach(function (selector) {
2869 Object.keys(selector).forEach(function (field) {
2870 var matcher = selector[field];
2871 if (typeof matcher !== 'object') {
2872 matcher = {$eq: matcher};
2873 }
2874
2875 if (isCombinationalField(field)) {
2876 if (matcher instanceof Array) {
2877 res$$1[field] = matcher.map(function (m) {
2878 return mergeAndedSelectors([m]);
2879 });
2880 } else {
2881 res$$1[field] = mergeAndedSelectors([matcher]);
2882 }
2883 } else {
2884 var fieldMatchers = res$$1[field] = res$$1[field] || {};
2885 Object.keys(matcher).forEach(function (operator) {
2886 var value = matcher[operator];
2887
2888 if (operator === '$gt' || operator === '$gte') {
2889 return mergeGtGte(operator, value, fieldMatchers);
2890 } else if (operator === '$lt' || operator === '$lte') {
2891 return mergeLtLte(operator, value, fieldMatchers);
2892 } else if (operator === '$ne') {
2893 return mergeNe(value, fieldMatchers);
2894 } else if (operator === '$eq') {
2895 return mergeEq(value, fieldMatchers);
2896 }
2897 fieldMatchers[operator] = value;
2898 });
2899 }
2900 });
2901 });
2902
2903 return res$$1;
2904}
2905
2906
2907
2908// collapse logically equivalent gt/gte values
2909function mergeGtGte(operator, value, fieldMatchers) {
2910 if (typeof fieldMatchers.$eq !== 'undefined') {
2911 return; // do nothing
2912 }
2913 if (typeof fieldMatchers.$gte !== 'undefined') {
2914 if (operator === '$gte') {
2915 if (value > fieldMatchers.$gte) { // more specificity
2916 fieldMatchers.$gte = value;
2917 }
2918 } else { // operator === '$gt'
2919 if (value >= fieldMatchers.$gte) { // more specificity
2920 delete fieldMatchers.$gte;
2921 fieldMatchers.$gt = value;
2922 }
2923 }
2924 } else if (typeof fieldMatchers.$gt !== 'undefined') {
2925 if (operator === '$gte') {
2926 if (value > fieldMatchers.$gt) { // more specificity
2927 delete fieldMatchers.$gt;
2928 fieldMatchers.$gte = value;
2929 }
2930 } else { // operator === '$gt'
2931 if (value > fieldMatchers.$gt) { // more specificity
2932 fieldMatchers.$gt = value;
2933 }
2934 }
2935 } else {
2936 fieldMatchers[operator] = value;
2937 }
2938}
2939
2940// collapse logically equivalent lt/lte values
2941function mergeLtLte(operator, value, fieldMatchers) {
2942 if (typeof fieldMatchers.$eq !== 'undefined') {
2943 return; // do nothing
2944 }
2945 if (typeof fieldMatchers.$lte !== 'undefined') {
2946 if (operator === '$lte') {
2947 if (value < fieldMatchers.$lte) { // more specificity
2948 fieldMatchers.$lte = value;
2949 }
2950 } else { // operator === '$gt'
2951 if (value <= fieldMatchers.$lte) { // more specificity
2952 delete fieldMatchers.$lte;
2953 fieldMatchers.$lt = value;
2954 }
2955 }
2956 } else if (typeof fieldMatchers.$lt !== 'undefined') {
2957 if (operator === '$lte') {
2958 if (value < fieldMatchers.$lt) { // more specificity
2959 delete fieldMatchers.$lt;
2960 fieldMatchers.$lte = value;
2961 }
2962 } else { // operator === '$gt'
2963 if (value < fieldMatchers.$lt) { // more specificity
2964 fieldMatchers.$lt = value;
2965 }
2966 }
2967 } else {
2968 fieldMatchers[operator] = value;
2969 }
2970}
2971
2972// combine $ne values into one array
2973function mergeNe(value, fieldMatchers) {
2974 if ('$ne' in fieldMatchers) {
2975 // there are many things this could "not" be
2976 fieldMatchers.$ne.push(value);
2977 } else { // doesn't exist yet
2978 fieldMatchers.$ne = [value];
2979 }
2980}
2981
2982// add $eq into the mix
2983function mergeEq(value, fieldMatchers) {
2984 // these all have less specificity than the $eq
2985 // TODO: check for user errors here
2986 delete fieldMatchers.$gt;
2987 delete fieldMatchers.$gte;
2988 delete fieldMatchers.$lt;
2989 delete fieldMatchers.$lte;
2990 delete fieldMatchers.$ne;
2991 fieldMatchers.$eq = value;
2992}
2993
2994
2995//
2996// normalize the selector
2997//
2998function massageSelector(input) {
2999 var result = clone(input);
3000 var wasAnded = false;
3001 if ('$and' in result) {
3002 result = mergeAndedSelectors(result['$and']);
3003 wasAnded = true;
3004 }
3005
3006 ['$or', '$nor'].forEach(function (orOrNor) {
3007 if (orOrNor in result) {
3008 // message each individual selector
3009 // e.g. {foo: 'bar'} becomes {foo: {$eq: 'bar'}}
3010 result[orOrNor].forEach(function (subSelector) {
3011 var fields = Object.keys(subSelector);
3012 for (var i = 0; i < fields.length; i++) {
3013 var field = fields[i];
3014 var matcher = subSelector[field];
3015 if (typeof matcher !== 'object' || matcher === null) {
3016 subSelector[field] = {$eq: matcher};
3017 }
3018 }
3019 });
3020 }
3021 });
3022
3023 if ('$not' in result) {
3024 //This feels a little like forcing, but it will work for now,
3025 //I would like to come back to this and make the merging of selectors a little more generic
3026 result['$not'] = mergeAndedSelectors([result['$not']]);
3027 }
3028
3029 var fields = Object.keys(result);
3030
3031 for (var i = 0; i < fields.length; i++) {
3032 var field = fields[i];
3033 var matcher = result[field];
3034
3035 if (typeof matcher !== 'object' || matcher === null) {
3036 matcher = {$eq: matcher};
3037 } else if ('$ne' in matcher && !wasAnded) {
3038 // I put these in an array, since there may be more than one
3039 // but in the "mergeAnded" operation, I already take care of that
3040 matcher.$ne = [matcher.$ne];
3041 }
3042 result[field] = matcher;
3043 }
3044
3045 return result;
3046}
3047
3048function pad(str, padWith, upToLength) {
3049 var padding = '';
3050 var targetLength = upToLength - str.length;
3051 /* istanbul ignore next */
3052 while (padding.length < targetLength) {
3053 padding += padWith;
3054 }
3055 return padding;
3056}
3057
3058function padLeft(str, padWith, upToLength) {
3059 var padding = pad(str, padWith, upToLength);
3060 return padding + str;
3061}
3062
3063var MIN_MAGNITUDE = -324; // verified by -Number.MIN_VALUE
3064var MAGNITUDE_DIGITS = 3; // ditto
3065var SEP = ''; // set to '_' for easier debugging
3066
3067function collate(a, b) {
3068
3069 if (a === b) {
3070 return 0;
3071 }
3072
3073 a = normalizeKey(a);
3074 b = normalizeKey(b);
3075
3076 var ai = collationIndex(a);
3077 var bi = collationIndex(b);
3078 if ((ai - bi) !== 0) {
3079 return ai - bi;
3080 }
3081 switch (typeof a) {
3082 case 'number':
3083 return a - b;
3084 case 'boolean':
3085 return a < b ? -1 : 1;
3086 case 'string':
3087 return stringCollate(a, b);
3088 }
3089 return Array.isArray(a) ? arrayCollate(a, b) : objectCollate(a, b);
3090}
3091
3092// couch considers null/NaN/Infinity/-Infinity === undefined,
3093// for the purposes of mapreduce indexes. also, dates get stringified.
3094function normalizeKey(key) {
3095 switch (typeof key) {
3096 case 'undefined':
3097 return null;
3098 case 'number':
3099 if (key === Infinity || key === -Infinity || isNaN(key)) {
3100 return null;
3101 }
3102 return key;
3103 case 'object':
3104 var origKey = key;
3105 if (Array.isArray(key)) {
3106 var len = key.length;
3107 key = new Array(len);
3108 for (var i = 0; i < len; i++) {
3109 key[i] = normalizeKey(origKey[i]);
3110 }
3111 /* istanbul ignore next */
3112 } else if (key instanceof Date) {
3113 return key.toJSON();
3114 } else if (key !== null) { // generic object
3115 key = {};
3116 for (var k in origKey) {
3117 if (origKey.hasOwnProperty(k)) {
3118 var val = origKey[k];
3119 if (typeof val !== 'undefined') {
3120 key[k] = normalizeKey(val);
3121 }
3122 }
3123 }
3124 }
3125 }
3126 return key;
3127}
3128
3129function indexify(key) {
3130 if (key !== null) {
3131 switch (typeof key) {
3132 case 'boolean':
3133 return key ? 1 : 0;
3134 case 'number':
3135 return numToIndexableString(key);
3136 case 'string':
3137 // We've to be sure that key does not contain \u0000
3138 // Do order-preserving replacements:
3139 // 0 -> 1, 1
3140 // 1 -> 1, 2
3141 // 2 -> 2, 2
3142 /* eslint-disable no-control-regex */
3143 return key
3144 .replace(/\u0002/g, '\u0002\u0002')
3145 .replace(/\u0001/g, '\u0001\u0002')
3146 .replace(/\u0000/g, '\u0001\u0001');
3147 /* eslint-enable no-control-regex */
3148 case 'object':
3149 var isArray = Array.isArray(key);
3150 var arr = isArray ? key : Object.keys(key);
3151 var i = -1;
3152 var len = arr.length;
3153 var result = '';
3154 if (isArray) {
3155 while (++i < len) {
3156 result += toIndexableString(arr[i]);
3157 }
3158 } else {
3159 while (++i < len) {
3160 var objKey = arr[i];
3161 result += toIndexableString(objKey) +
3162 toIndexableString(key[objKey]);
3163 }
3164 }
3165 return result;
3166 }
3167 }
3168 return '';
3169}
3170
3171// convert the given key to a string that would be appropriate
3172// for lexical sorting, e.g. within a database, where the
3173// sorting is the same given by the collate() function.
3174function toIndexableString(key) {
3175 var zero = '\u0000';
3176 key = normalizeKey(key);
3177 return collationIndex(key) + SEP + indexify(key) + zero;
3178}
3179
3180function parseNumber(str, i) {
3181 var originalIdx = i;
3182 var num;
3183 var zero = str[i] === '1';
3184 if (zero) {
3185 num = 0;
3186 i++;
3187 } else {
3188 var neg = str[i] === '0';
3189 i++;
3190 var numAsString = '';
3191 var magAsString = str.substring(i, i + MAGNITUDE_DIGITS);
3192 var magnitude = parseInt(magAsString, 10) + MIN_MAGNITUDE;
3193 /* istanbul ignore next */
3194 if (neg) {
3195 magnitude = -magnitude;
3196 }
3197 i += MAGNITUDE_DIGITS;
3198 while (true) {
3199 var ch = str[i];
3200 if (ch === '\u0000') {
3201 break;
3202 } else {
3203 numAsString += ch;
3204 }
3205 i++;
3206 }
3207 numAsString = numAsString.split('.');
3208 if (numAsString.length === 1) {
3209 num = parseInt(numAsString, 10);
3210 } else {
3211 /* istanbul ignore next */
3212 num = parseFloat(numAsString[0] + '.' + numAsString[1]);
3213 }
3214 /* istanbul ignore next */
3215 if (neg) {
3216 num = num - 10;
3217 }
3218 /* istanbul ignore next */
3219 if (magnitude !== 0) {
3220 // parseFloat is more reliable than pow due to rounding errors
3221 // e.g. Number.MAX_VALUE would return Infinity if we did
3222 // num * Math.pow(10, magnitude);
3223 num = parseFloat(num + 'e' + magnitude);
3224 }
3225 }
3226 return {num: num, length : i - originalIdx};
3227}
3228
3229// move up the stack while parsing
3230// this function moved outside of parseIndexableString for performance
3231function pop(stack, metaStack) {
3232 var obj$$1 = stack.pop();
3233
3234 if (metaStack.length) {
3235 var lastMetaElement = metaStack[metaStack.length - 1];
3236 if (obj$$1 === lastMetaElement.element) {
3237 // popping a meta-element, e.g. an object whose value is another object
3238 metaStack.pop();
3239 lastMetaElement = metaStack[metaStack.length - 1];
3240 }
3241 var element = lastMetaElement.element;
3242 var lastElementIndex = lastMetaElement.index;
3243 if (Array.isArray(element)) {
3244 element.push(obj$$1);
3245 } else if (lastElementIndex === stack.length - 2) { // obj with key+value
3246 var key = stack.pop();
3247 element[key] = obj$$1;
3248 } else {
3249 stack.push(obj$$1); // obj with key only
3250 }
3251 }
3252}
3253
3254function parseIndexableString(str) {
3255 var stack = [];
3256 var metaStack = []; // stack for arrays and objects
3257 var i = 0;
3258
3259 /*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
3260 while (true) {
3261 var collationIndex = str[i++];
3262 if (collationIndex === '\u0000') {
3263 if (stack.length === 1) {
3264 return stack.pop();
3265 } else {
3266 pop(stack, metaStack);
3267 continue;
3268 }
3269 }
3270 switch (collationIndex) {
3271 case '1':
3272 stack.push(null);
3273 break;
3274 case '2':
3275 stack.push(str[i] === '1');
3276 i++;
3277 break;
3278 case '3':
3279 var parsedNum = parseNumber(str, i);
3280 stack.push(parsedNum.num);
3281 i += parsedNum.length;
3282 break;
3283 case '4':
3284 var parsedStr = '';
3285 /*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
3286 while (true) {
3287 var ch = str[i];
3288 if (ch === '\u0000') {
3289 break;
3290 }
3291 parsedStr += ch;
3292 i++;
3293 }
3294 // perform the reverse of the order-preserving replacement
3295 // algorithm (see above)
3296 /* eslint-disable no-control-regex */
3297 parsedStr = parsedStr.replace(/\u0001\u0001/g, '\u0000')
3298 .replace(/\u0001\u0002/g, '\u0001')
3299 .replace(/\u0002\u0002/g, '\u0002');
3300 /* eslint-enable no-control-regex */
3301 stack.push(parsedStr);
3302 break;
3303 case '5':
3304 var arrayElement = { element: [], index: stack.length };
3305 stack.push(arrayElement.element);
3306 metaStack.push(arrayElement);
3307 break;
3308 case '6':
3309 var objElement = { element: {}, index: stack.length };
3310 stack.push(objElement.element);
3311 metaStack.push(objElement);
3312 break;
3313 /* istanbul ignore next */
3314 default:
3315 throw new Error(
3316 'bad collationIndex or unexpectedly reached end of input: ' +
3317 collationIndex);
3318 }
3319 }
3320}
3321
3322function arrayCollate(a, b) {
3323 var len = Math.min(a.length, b.length);
3324 for (var i = 0; i < len; i++) {
3325 var sort = collate(a[i], b[i]);
3326 if (sort !== 0) {
3327 return sort;
3328 }
3329 }
3330 return (a.length === b.length) ? 0 :
3331 (a.length > b.length) ? 1 : -1;
3332}
3333function stringCollate(a, b) {
3334 // See: https://github.com/daleharvey/pouchdb/issues/40
3335 // This is incompatible with the CouchDB implementation, but its the
3336 // best we can do for now
3337 return (a === b) ? 0 : ((a > b) ? 1 : -1);
3338}
3339function objectCollate(a, b) {
3340 var ak = Object.keys(a), bk = Object.keys(b);
3341 var len = Math.min(ak.length, bk.length);
3342 for (var i = 0; i < len; i++) {
3343 // First sort the keys
3344 var sort = collate(ak[i], bk[i]);
3345 if (sort !== 0) {
3346 return sort;
3347 }
3348 // if the keys are equal sort the values
3349 sort = collate(a[ak[i]], b[bk[i]]);
3350 if (sort !== 0) {
3351 return sort;
3352 }
3353
3354 }
3355 return (ak.length === bk.length) ? 0 :
3356 (ak.length > bk.length) ? 1 : -1;
3357}
3358// The collation is defined by erlangs ordered terms
3359// the atoms null, true, false come first, then numbers, strings,
3360// arrays, then objects
3361// null/undefined/NaN/Infinity/-Infinity are all considered null
3362function collationIndex(x) {
3363 var id = ['boolean', 'number', 'string', 'object'];
3364 var idx = id.indexOf(typeof x);
3365 //false if -1 otherwise true, but fast!!!!1
3366 if (~idx) {
3367 if (x === null) {
3368 return 1;
3369 }
3370 if (Array.isArray(x)) {
3371 return 5;
3372 }
3373 return idx < 3 ? (idx + 2) : (idx + 3);
3374 }
3375 /* istanbul ignore next */
3376 if (Array.isArray(x)) {
3377 return 5;
3378 }
3379}
3380
3381// conversion:
3382// x yyy zz...zz
3383// x = 0 for negative, 1 for 0, 2 for positive
3384// y = exponent (for negative numbers negated) moved so that it's >= 0
3385// z = mantisse
3386function numToIndexableString(num) {
3387
3388 if (num === 0) {
3389 return '1';
3390 }
3391
3392 // convert number to exponential format for easier and
3393 // more succinct string sorting
3394 var expFormat = num.toExponential().split(/e\+?/);
3395 var magnitude = parseInt(expFormat[1], 10);
3396
3397 var neg = num < 0;
3398
3399 var result = neg ? '0' : '2';
3400
3401 // first sort by magnitude
3402 // it's easier if all magnitudes are positive
3403 var magForComparison = ((neg ? -magnitude : magnitude) - MIN_MAGNITUDE);
3404 var magString = padLeft((magForComparison).toString(), '0', MAGNITUDE_DIGITS);
3405
3406 result += SEP + magString;
3407
3408 // then sort by the factor
3409 var factor = Math.abs(parseFloat(expFormat[0])); // [1..10)
3410 /* istanbul ignore next */
3411 if (neg) { // for negative reverse ordering
3412 factor = 10 - factor;
3413 }
3414
3415 var factorStr = factor.toFixed(20);
3416
3417 // strip zeros from the end
3418 factorStr = factorStr.replace(/\.?0+$/, '');
3419
3420 result += SEP + factorStr;
3421
3422 return result;
3423}
3424
3425// create a comparator based on the sort object
3426function createFieldSorter(sort) {
3427
3428 function getFieldValuesAsArray(doc) {
3429 return sort.map(function (sorting) {
3430 var fieldName = getKey(sorting);
3431 var parsedField = parseField(fieldName);
3432 var docFieldValue = getFieldFromDoc(doc, parsedField);
3433 return docFieldValue;
3434 });
3435 }
3436
3437 return function (aRow, bRow) {
3438 var aFieldValues = getFieldValuesAsArray(aRow.doc);
3439 var bFieldValues = getFieldValuesAsArray(bRow.doc);
3440 var collation = collate(aFieldValues, bFieldValues);
3441 if (collation !== 0) {
3442 return collation;
3443 }
3444 // this is what mango seems to do
3445 return compare$1(aRow.doc._id, bRow.doc._id);
3446 };
3447}
3448
3449function filterInMemoryFields(rows, requestDef, inMemoryFields) {
3450 rows = rows.filter(function (row) {
3451 return rowFilter(row.doc, requestDef.selector, inMemoryFields);
3452 });
3453
3454 if (requestDef.sort) {
3455 // in-memory sort
3456 var fieldSorter = createFieldSorter(requestDef.sort);
3457 rows = rows.sort(fieldSorter);
3458 if (typeof requestDef.sort[0] !== 'string' &&
3459 getValue(requestDef.sort[0]) === 'desc') {
3460 rows = rows.reverse();
3461 }
3462 }
3463
3464 if ('limit' in requestDef || 'skip' in requestDef) {
3465 // have to do the limit in-memory
3466 var skip = requestDef.skip || 0;
3467 var limit = ('limit' in requestDef ? requestDef.limit : rows.length) + skip;
3468 rows = rows.slice(skip, limit);
3469 }
3470 return rows;
3471}
3472
3473function rowFilter(doc, selector, inMemoryFields) {
3474 return inMemoryFields.every(function (field) {
3475 var matcher = selector[field];
3476 var parsedField = parseField(field);
3477 var docFieldValue = getFieldFromDoc(doc, parsedField);
3478 if (isCombinationalField(field)) {
3479 return matchCominationalSelector(field, matcher, doc);
3480 }
3481
3482 return matchSelector(matcher, doc, parsedField, docFieldValue);
3483 });
3484}
3485
3486function matchSelector(matcher, doc, parsedField, docFieldValue) {
3487 if (!matcher) {
3488 // no filtering necessary; this field is just needed for sorting
3489 return true;
3490 }
3491
3492 return Object.keys(matcher).every(function (userOperator) {
3493 var userValue = matcher[userOperator];
3494 return match(userOperator, doc, userValue, parsedField, docFieldValue);
3495 });
3496}
3497
3498function matchCominationalSelector(field, matcher, doc) {
3499
3500 if (field === '$or') {
3501 return matcher.some(function (orMatchers) {
3502 return rowFilter(doc, orMatchers, Object.keys(orMatchers));
3503 });
3504 }
3505
3506 if (field === '$not') {
3507 return !rowFilter(doc, matcher, Object.keys(matcher));
3508 }
3509
3510 //`$nor`
3511 return !matcher.find(function (orMatchers) {
3512 return rowFilter(doc, orMatchers, Object.keys(orMatchers));
3513 });
3514
3515}
3516
3517function match(userOperator, doc, userValue, parsedField, docFieldValue) {
3518 if (!matchers[userOperator]) {
3519 throw new Error('unknown operator "' + userOperator +
3520 '" - should be one of $eq, $lte, $lt, $gt, $gte, $exists, $ne, $in, ' +
3521 '$nin, $size, $mod, $regex, $elemMatch, $type, $allMatch or $all');
3522 }
3523 return matchers[userOperator](doc, userValue, parsedField, docFieldValue);
3524}
3525
3526function fieldExists(docFieldValue) {
3527 return typeof docFieldValue !== 'undefined' && docFieldValue !== null;
3528}
3529
3530function fieldIsNotUndefined(docFieldValue) {
3531 return typeof docFieldValue !== 'undefined';
3532}
3533
3534function modField(docFieldValue, userValue) {
3535 var divisor = userValue[0];
3536 var mod = userValue[1];
3537 if (divisor === 0) {
3538 throw new Error('Bad divisor, cannot divide by zero');
3539 }
3540
3541 if (parseInt(divisor, 10) !== divisor ) {
3542 throw new Error('Divisor is not an integer');
3543 }
3544
3545 if (parseInt(mod, 10) !== mod ) {
3546 throw new Error('Modulus is not an integer');
3547 }
3548
3549 if (parseInt(docFieldValue, 10) !== docFieldValue) {
3550 return false;
3551 }
3552
3553 return docFieldValue % divisor === mod;
3554}
3555
3556function arrayContainsValue(docFieldValue, userValue) {
3557 return userValue.some(function (val) {
3558 if (docFieldValue instanceof Array) {
3559 return docFieldValue.indexOf(val) > -1;
3560 }
3561
3562 return docFieldValue === val;
3563 });
3564}
3565
3566function arrayContainsAllValues(docFieldValue, userValue) {
3567 return userValue.every(function (val) {
3568 return docFieldValue.indexOf(val) > -1;
3569 });
3570}
3571
3572function arraySize(docFieldValue, userValue) {
3573 return docFieldValue.length === userValue;
3574}
3575
3576function regexMatch(docFieldValue, userValue) {
3577 var re = new RegExp(userValue);
3578
3579 return re.test(docFieldValue);
3580}
3581
3582function typeMatch(docFieldValue, userValue) {
3583
3584 switch (userValue) {
3585 case 'null':
3586 return docFieldValue === null;
3587 case 'boolean':
3588 return typeof (docFieldValue) === 'boolean';
3589 case 'number':
3590 return typeof (docFieldValue) === 'number';
3591 case 'string':
3592 return typeof (docFieldValue) === 'string';
3593 case 'array':
3594 return docFieldValue instanceof Array;
3595 case 'object':
3596 return ({}).toString.call(docFieldValue) === '[object Object]';
3597 }
3598
3599 throw new Error(userValue + ' not supported as a type.' +
3600 'Please use one of object, string, array, number, boolean or null.');
3601
3602}
3603
3604var matchers = {
3605
3606 '$elemMatch': function (doc, userValue, parsedField, docFieldValue) {
3607 if (!Array.isArray(docFieldValue)) {
3608 return false;
3609 }
3610
3611 if (docFieldValue.length === 0) {
3612 return false;
3613 }
3614
3615 if (typeof docFieldValue[0] === 'object') {
3616 return docFieldValue.some(function (val) {
3617 return rowFilter(val, userValue, Object.keys(userValue));
3618 });
3619 }
3620
3621 return docFieldValue.some(function (val) {
3622 return matchSelector(userValue, doc, parsedField, val);
3623 });
3624 },
3625
3626 '$allMatch': function (doc, userValue, parsedField, docFieldValue) {
3627 if (!Array.isArray(docFieldValue)) {
3628 return false;
3629 }
3630
3631 /* istanbul ignore next */
3632 if (docFieldValue.length === 0) {
3633 return false;
3634 }
3635
3636 if (typeof docFieldValue[0] === 'object') {
3637 return docFieldValue.every(function (val) {
3638 return rowFilter(val, userValue, Object.keys(userValue));
3639 });
3640 }
3641
3642 return docFieldValue.every(function (val) {
3643 return matchSelector(userValue, doc, parsedField, val);
3644 });
3645 },
3646
3647 '$eq': function (doc, userValue, parsedField, docFieldValue) {
3648 return fieldIsNotUndefined(docFieldValue) && collate(docFieldValue, userValue) === 0;
3649 },
3650
3651 '$gte': function (doc, userValue, parsedField, docFieldValue) {
3652 return fieldIsNotUndefined(docFieldValue) && collate(docFieldValue, userValue) >= 0;
3653 },
3654
3655 '$gt': function (doc, userValue, parsedField, docFieldValue) {
3656 return fieldIsNotUndefined(docFieldValue) && collate(docFieldValue, userValue) > 0;
3657 },
3658
3659 '$lte': function (doc, userValue, parsedField, docFieldValue) {
3660 return fieldIsNotUndefined(docFieldValue) && collate(docFieldValue, userValue) <= 0;
3661 },
3662
3663 '$lt': function (doc, userValue, parsedField, docFieldValue) {
3664 return fieldIsNotUndefined(docFieldValue) && collate(docFieldValue, userValue) < 0;
3665 },
3666
3667 '$exists': function (doc, userValue, parsedField, docFieldValue) {
3668 //a field that is null is still considered to exist
3669 if (userValue) {
3670 return fieldIsNotUndefined(docFieldValue);
3671 }
3672
3673 return !fieldIsNotUndefined(docFieldValue);
3674 },
3675
3676 '$mod': function (doc, userValue, parsedField, docFieldValue) {
3677 return fieldExists(docFieldValue) && modField(docFieldValue, userValue);
3678 },
3679
3680 '$ne': function (doc, userValue, parsedField, docFieldValue) {
3681 return userValue.every(function (neValue) {
3682 return collate(docFieldValue, neValue) !== 0;
3683 });
3684 },
3685 '$in': function (doc, userValue, parsedField, docFieldValue) {
3686 return fieldExists(docFieldValue) && arrayContainsValue(docFieldValue, userValue);
3687 },
3688
3689 '$nin': function (doc, userValue, parsedField, docFieldValue) {
3690 return fieldExists(docFieldValue) && !arrayContainsValue(docFieldValue, userValue);
3691 },
3692
3693 '$size': function (doc, userValue, parsedField, docFieldValue) {
3694 return fieldExists(docFieldValue) && arraySize(docFieldValue, userValue);
3695 },
3696
3697 '$all': function (doc, userValue, parsedField, docFieldValue) {
3698 return Array.isArray(docFieldValue) && arrayContainsAllValues(docFieldValue, userValue);
3699 },
3700
3701 '$regex': function (doc, userValue, parsedField, docFieldValue) {
3702 return fieldExists(docFieldValue) && regexMatch(docFieldValue, userValue);
3703 },
3704
3705 '$type': function (doc, userValue, parsedField, docFieldValue) {
3706 return typeMatch(docFieldValue, userValue);
3707 }
3708};
3709
3710// return true if the given doc matches the supplied selector
3711function matchesSelector(doc, selector) {
3712 /* istanbul ignore if */
3713 if (typeof selector !== 'object') {
3714 // match the CouchDB error message
3715 throw new Error('Selector error: expected a JSON object');
3716 }
3717
3718 selector = massageSelector(selector);
3719 var row = {
3720 'doc': doc
3721 };
3722
3723 var rowsMatched = filterInMemoryFields([row], { 'selector': selector }, Object.keys(selector));
3724 return rowsMatched && rowsMatched.length === 1;
3725}
3726
3727function evalFilter(input) {
3728 var code = '(function() {\n"use strict";\nreturn ' + input + '\n})()';
3729
3730 return vm.runInNewContext(code);
3731}
3732
3733function evalView(input) {
3734 var code = [
3735 '"use strict";',
3736 'var emitted = false;',
3737 'var emit = function (a, b) {',
3738 ' emitted = true;',
3739 '};',
3740 'var view = ' + input + ';',
3741 'view(doc);',
3742 'if (emitted) {',
3743 ' return true;',
3744 '}'
3745 ].join('\n');
3746
3747 return vm.runInNewContext('(function(doc) {\n' + code + '\n})');
3748}
3749
3750function validate(opts, callback) {
3751 if (opts.selector) {
3752 if (opts.filter && opts.filter !== '_selector') {
3753 var filterName = typeof opts.filter === 'string' ?
3754 opts.filter : 'function';
3755 return callback(new Error('selector invalid for filter "' + filterName + '"'));
3756 }
3757 }
3758 callback();
3759}
3760
3761function normalize(opts) {
3762 if (opts.view && !opts.filter) {
3763 opts.filter = '_view';
3764 }
3765
3766 if (opts.selector && !opts.filter) {
3767 opts.filter = '_selector';
3768 }
3769
3770 if (opts.filter && typeof opts.filter === 'string') {
3771 if (opts.filter === '_view') {
3772 opts.view = normalizeDesignDocFunctionName(opts.view);
3773 } else {
3774 opts.filter = normalizeDesignDocFunctionName(opts.filter);
3775 }
3776 }
3777}
3778
3779function shouldFilter(changesHandler, opts) {
3780 return opts.filter && typeof opts.filter === 'string' &&
3781 !opts.doc_ids && !isRemote(changesHandler.db);
3782}
3783
3784function filter(changesHandler, opts) {
3785 var callback = opts.complete;
3786 if (opts.filter === '_view') {
3787 if (!opts.view || typeof opts.view !== 'string') {
3788 var err = createError(BAD_REQUEST,
3789 '`view` filter parameter not found or invalid.');
3790 return callback(err);
3791 }
3792 // fetch a view from a design doc, make it behave like a filter
3793 var viewName = parseDesignDocFunctionName(opts.view);
3794 changesHandler.db.get('_design/' + viewName[0], function (err, ddoc) {
3795 /* istanbul ignore if */
3796 if (changesHandler.isCancelled) {
3797 return callback(null, {status: 'cancelled'});
3798 }
3799 /* istanbul ignore next */
3800 if (err) {
3801 return callback(generateErrorFromResponse(err));
3802 }
3803 var mapFun = ddoc && ddoc.views && ddoc.views[viewName[1]] &&
3804 ddoc.views[viewName[1]].map;
3805 if (!mapFun) {
3806 return callback(createError(MISSING_DOC,
3807 (ddoc.views ? 'missing json key: ' + viewName[1] :
3808 'missing json key: views')));
3809 }
3810 opts.filter = evalView(mapFun);
3811 changesHandler.doChanges(opts);
3812 });
3813 } else if (opts.selector) {
3814 opts.filter = function (doc) {
3815 return matchesSelector(doc, opts.selector);
3816 };
3817 changesHandler.doChanges(opts);
3818 } else {
3819 // fetch a filter from a design doc
3820 var filterName = parseDesignDocFunctionName(opts.filter);
3821 changesHandler.db.get('_design/' + filterName[0], function (err, ddoc) {
3822 /* istanbul ignore if */
3823 if (changesHandler.isCancelled) {
3824 return callback(null, {status: 'cancelled'});
3825 }
3826 /* istanbul ignore next */
3827 if (err) {
3828 return callback(generateErrorFromResponse(err));
3829 }
3830 var filterFun = ddoc && ddoc.filters && ddoc.filters[filterName[1]];
3831 if (!filterFun) {
3832 return callback(createError(MISSING_DOC,
3833 ((ddoc && ddoc.filters) ? 'missing json key: ' + filterName[1]
3834 : 'missing json key: filters')));
3835 }
3836 opts.filter = evalFilter(filterFun);
3837 changesHandler.doChanges(opts);
3838 });
3839 }
3840}
3841
3842function applyChangesFilterPlugin(PouchDB) {
3843 PouchDB._changesFilterPlugin = {
3844 validate: validate,
3845 normalize: normalize,
3846 shouldFilter: shouldFilter,
3847 filter: filter
3848 };
3849}
3850
3851// TODO: remove from pouchdb-core (breaking)
3852PouchDB.plugin(applyChangesFilterPlugin);
3853
3854PouchDB.version = version;
3855
3856function isFunction(f) {
3857 return 'function' === typeof f;
3858}
3859
3860function getPrefix(db) {
3861 if (isFunction(db.prefix)) {
3862 return db.prefix();
3863 }
3864 return db;
3865}
3866
3867function clone$1(_obj) {
3868 var obj$$1 = {};
3869 for (var k in _obj) {
3870 obj$$1[k] = _obj[k];
3871 }
3872 return obj$$1;
3873}
3874
3875function nut(db, precodec, codec) {
3876 function encodePrefix(prefix, key, opts1, opts2) {
3877 return precodec.encode([ prefix, codec.encodeKey(key, opts1, opts2 ) ]);
3878 }
3879
3880 function addEncodings(op, prefix) {
3881 if (prefix && prefix.options) {
3882 op.keyEncoding =
3883 op.keyEncoding || prefix.options.keyEncoding;
3884 op.valueEncoding =
3885 op.valueEncoding || prefix.options.valueEncoding;
3886 }
3887 return op;
3888 }
3889
3890 db.open(function () { /* no-op */});
3891
3892 return {
3893 apply: function (ops, opts, cb) {
3894 opts = opts || {};
3895
3896 var batch = [];
3897 var i = -1;
3898 var len = ops.length;
3899
3900 while (++i < len) {
3901 var op = ops[i];
3902 addEncodings(op, op.prefix);
3903 op.prefix = getPrefix(op.prefix);
3904 batch.push({
3905 key: encodePrefix(op.prefix, op.key, opts, op),
3906 value: op.type !== 'del' && codec.encodeValue(op.value, opts, op),
3907 type: op.type
3908 });
3909 }
3910 db.db.batch(batch, opts, cb);
3911 },
3912 get: function (key, prefix, opts, cb) {
3913 opts.asBuffer = codec.valueAsBuffer(opts);
3914 return db.db.get(
3915 encodePrefix(prefix, key, opts),
3916 opts,
3917 function (err, value) {
3918 if (err) {
3919 cb(err);
3920 } else {
3921 cb(null, codec.decodeValue(value, opts));
3922 }
3923 }
3924 );
3925 },
3926 createDecoder: function (opts) {
3927 return function (key, value) {
3928 return {
3929 key: codec.decodeKey(precodec.decode(key)[1], opts),
3930 value: codec.decodeValue(value, opts)
3931 };
3932 };
3933 },
3934 isClosed: function isClosed() {
3935 return db.isClosed();
3936 },
3937 close: function close(cb) {
3938 return db.close(cb);
3939 },
3940 iterator: function (_opts) {
3941 var opts = clone$1(_opts || {});
3942 var prefix = _opts.prefix || [];
3943
3944 function encodeKey(key) {
3945 return encodePrefix(prefix, key, opts, {});
3946 }
3947
3948 ltgt.toLtgt(_opts, opts, encodeKey, precodec.lowerBound, precodec.upperBound);
3949
3950 // if these legacy values are in the options, remove them
3951
3952 opts.prefix = null;
3953
3954 //************************************************
3955 //hard coded defaults, for now...
3956 //TODO: pull defaults and encoding out of levelup.
3957 opts.keyAsBuffer = opts.valueAsBuffer = false;
3958 //************************************************
3959
3960
3961 //this is vital, otherwise limit: undefined will
3962 //create an empty stream.
3963 /* istanbul ignore next */
3964 if ('number' !== typeof opts.limit) {
3965 opts.limit = -1;
3966 }
3967
3968 opts.keyAsBuffer = precodec.buffer;
3969 opts.valueAsBuffer = codec.valueAsBuffer(opts);
3970
3971 function wrapIterator(iterator) {
3972 return {
3973 next: function (cb) {
3974 return iterator.next(cb);
3975 },
3976 end: function (cb) {
3977 iterator.end(cb);
3978 }
3979 };
3980 }
3981
3982 return wrapIterator(db.db.iterator(opts));
3983 }
3984 };
3985}
3986
3987function NotFoundError() {
3988 Error.call(this);
3989}
3990
3991inherits(NotFoundError, Error);
3992
3993NotFoundError.prototype.name = 'NotFoundError';
3994
3995var EventEmitter$1 = events.EventEmitter;
3996var version$1 = "6.5.4";
3997
3998var NOT_FOUND_ERROR = new NotFoundError();
3999
4000var sublevel = function (nut, prefix, createStream, options) {
4001 var emitter = new EventEmitter$1();
4002 emitter.sublevels = {};
4003 emitter.options = options;
4004
4005 emitter.version = version$1;
4006
4007 emitter.methods = {};
4008 prefix = prefix || [];
4009
4010 function mergeOpts(opts) {
4011 var o = {};
4012 var k;
4013 if (options) {
4014 for (k in options) {
4015 if (typeof options[k] !== 'undefined') {
4016 o[k] = options[k];
4017 }
4018 }
4019 }
4020 if (opts) {
4021 for (k in opts) {
4022 if (typeof opts[k] !== 'undefined') {
4023 o[k] = opts[k];
4024 }
4025 }
4026 }
4027 return o;
4028 }
4029
4030 emitter.put = function (key, value, opts, cb) {
4031 if ('function' === typeof opts) {
4032 cb = opts;
4033 opts = {};
4034 }
4035
4036 nut.apply([{
4037 key: key, value: value,
4038 prefix: prefix.slice(), type: 'put'
4039 }], mergeOpts(opts), function (err) {
4040 /* istanbul ignore next */
4041 if (err) {
4042 return cb(err);
4043 }
4044 emitter.emit('put', key, value);
4045 cb(null);
4046 });
4047 };
4048
4049 emitter.prefix = function () {
4050 return prefix.slice();
4051 };
4052
4053 emitter.batch = function (ops, opts, cb) {
4054 if ('function' === typeof opts) {
4055 cb = opts;
4056 opts = {};
4057 }
4058
4059 ops = ops.map(function (op) {
4060 return {
4061 key: op.key,
4062 value: op.value,
4063 prefix: op.prefix || prefix,
4064 keyEncoding: op.keyEncoding, // *
4065 valueEncoding: op.valueEncoding, // * (TODO: encodings on sublevel)
4066 type: op.type
4067 };
4068 });
4069
4070 nut.apply(ops, mergeOpts(opts), function (err) {
4071 /* istanbul ignore next */
4072 if (err) {
4073 return cb(err);
4074 }
4075 emitter.emit('batch', ops);
4076 cb(null);
4077 });
4078 };
4079
4080 emitter.get = function (key, opts, cb) {
4081 /* istanbul ignore else */
4082 if ('function' === typeof opts) {
4083 cb = opts;
4084 opts = {};
4085 }
4086 nut.get(key, prefix, mergeOpts(opts), function (err, value) {
4087 if (err) {
4088 cb(NOT_FOUND_ERROR);
4089 } else {
4090 cb(null, value);
4091 }
4092 });
4093 };
4094
4095 emitter.sublevel = function (name, opts) {
4096 return emitter.sublevels[name] =
4097 emitter.sublevels[name] || sublevel(nut, prefix.concat(name), createStream, mergeOpts(opts));
4098 };
4099
4100 emitter.readStream = emitter.createReadStream = function (opts) {
4101 opts = mergeOpts(opts);
4102 opts.prefix = prefix;
4103 var stream;
4104 var it = nut.iterator(opts);
4105
4106 stream = createStream(opts, nut.createDecoder(opts));
4107 stream.setIterator(it);
4108
4109 return stream;
4110 };
4111
4112 emitter.close = function (cb) {
4113 nut.close(cb);
4114 };
4115
4116 emitter.isOpen = nut.isOpen;
4117 emitter.isClosed = nut.isClosed;
4118
4119 return emitter;
4120};
4121
4122/* Copyright (c) 2012-2014 LevelUP contributors
4123 * See list at <https://github.com/rvagg/node-levelup#contributing>
4124 * MIT License <https://github.com/rvagg/node-levelup/blob/master/LICENSE.md>
4125 */
4126
4127var Readable = ReadableStreamCore.Readable;
4128
4129function ReadStream(options, makeData) {
4130 if (!(this instanceof ReadStream)) {
4131 return new ReadStream(options, makeData);
4132 }
4133
4134 Readable.call(this, { objectMode: true, highWaterMark: options.highWaterMark });
4135
4136 // purely to keep `db` around until we're done so it's not GCed if the user doesn't keep a ref
4137
4138 this._waiting = false;
4139 this._options = options;
4140 this._makeData = makeData;
4141}
4142
4143inherits(ReadStream, Readable);
4144
4145ReadStream.prototype.setIterator = function (it) {
4146 this._iterator = it;
4147 /* istanbul ignore if */
4148 if (this._destroyed) {
4149 return it.end(function () {});
4150 }
4151 /* istanbul ignore if */
4152 if (this._waiting) {
4153 this._waiting = false;
4154 return this._read();
4155 }
4156 return this;
4157};
4158
4159ReadStream.prototype._read = function read() {
4160 var self = this;
4161 /* istanbul ignore if */
4162 if (self._destroyed) {
4163 return;
4164 }
4165 /* istanbul ignore if */
4166 if (!self._iterator) {
4167 return this._waiting = true;
4168 }
4169
4170 self._iterator.next(function (err, key, value) {
4171 if (err || (key === undefined && value === undefined)) {
4172 if (!err && !self._destroyed) {
4173 self.push(null);
4174 }
4175 return self._cleanup(err);
4176 }
4177
4178
4179 value = self._makeData(key, value);
4180 if (!self._destroyed) {
4181 self.push(value);
4182 }
4183 });
4184};
4185
4186ReadStream.prototype._cleanup = function (err) {
4187 if (this._destroyed) {
4188 return;
4189 }
4190
4191 this._destroyed = true;
4192
4193 var self = this;
4194 /* istanbul ignore if */
4195 if (err && err.message !== 'iterator has ended') {
4196 self.emit('error', err);
4197 }
4198
4199 /* istanbul ignore else */
4200 if (self._iterator) {
4201 self._iterator.end(function () {
4202 self._iterator = null;
4203 self.emit('close');
4204 });
4205 } else {
4206 self.emit('close');
4207 }
4208};
4209
4210ReadStream.prototype.destroy = function () {
4211 this._cleanup();
4212};
4213
4214var precodec = {
4215 encode: function (decodedKey) {
4216 return '\xff' + decodedKey[0] + '\xff' + decodedKey[1];
4217 },
4218 decode: function (encodedKeyAsBuffer) {
4219 var str = encodedKeyAsBuffer.toString();
4220 var idx = str.indexOf('\xff', 1);
4221 return [str.substring(1, idx), str.substring(idx + 1)];
4222 },
4223 lowerBound: '\x00',
4224 upperBound: '\xff'
4225};
4226
4227var codec = new Codec();
4228
4229function sublevelPouch(db) {
4230 return sublevel(nut(db, precodec, codec), [], ReadStream, db.options);
4231}
4232
4233function allDocsKeysQuery(api, opts) {
4234 var keys = opts.keys;
4235 var finalResults = {
4236 offset: opts.skip
4237 };
4238 return Promise.all(keys.map(function (key) {
4239 var subOpts = $inject_Object_assign({key: key, deleted: 'ok'}, opts);
4240 ['limit', 'skip', 'keys'].forEach(function (optKey) {
4241 delete subOpts[optKey];
4242 });
4243 return new Promise(function (resolve, reject) {
4244 api._allDocs(subOpts, function (err, res$$1) {
4245 /* istanbul ignore if */
4246 if (err) {
4247 return reject(err);
4248 }
4249 /* istanbul ignore if */
4250 if (opts.update_seq && res$$1.update_seq !== undefined) {
4251 finalResults.update_seq = res$$1.update_seq;
4252 }
4253 finalResults.total_rows = res$$1.total_rows;
4254 resolve(res$$1.rows[0] || {key: key, error: 'not_found'});
4255 });
4256 });
4257 })).then(function (results) {
4258 finalResults.rows = results;
4259 return finalResults;
4260 });
4261}
4262
4263function toObject(array) {
4264 return array.reduce(function (obj$$1, item) {
4265 obj$$1[item] = true;
4266 return obj$$1;
4267 }, {});
4268}
4269// List of top level reserved words for doc
4270var reservedWords = toObject([
4271 '_id',
4272 '_rev',
4273 '_attachments',
4274 '_deleted',
4275 '_revisions',
4276 '_revs_info',
4277 '_conflicts',
4278 '_deleted_conflicts',
4279 '_local_seq',
4280 '_rev_tree',
4281 //replication documents
4282 '_replication_id',
4283 '_replication_state',
4284 '_replication_state_time',
4285 '_replication_state_reason',
4286 '_replication_stats',
4287 // Specific to Couchbase Sync Gateway
4288 '_removed'
4289]);
4290
4291// List of reserved words that should end up the document
4292var dataWords = toObject([
4293 '_attachments',
4294 //replication documents
4295 '_replication_id',
4296 '_replication_state',
4297 '_replication_state_time',
4298 '_replication_state_reason',
4299 '_replication_stats'
4300]);
4301
4302function parseRevisionInfo(rev) {
4303 if (!/^\d+-./.test(rev)) {
4304 return createError(INVALID_REV);
4305 }
4306 var idx = rev.indexOf('-');
4307 var left = rev.substring(0, idx);
4308 var right = rev.substring(idx + 1);
4309 return {
4310 prefix: parseInt(left, 10),
4311 id: right
4312 };
4313}
4314
4315function makeRevTreeFromRevisions(revisions, opts) {
4316 var pos = revisions.start - revisions.ids.length + 1;
4317
4318 var revisionIds = revisions.ids;
4319 var ids = [revisionIds[0], opts, []];
4320
4321 for (var i = 1, len = revisionIds.length; i < len; i++) {
4322 ids = [revisionIds[i], {status: 'missing'}, [ids]];
4323 }
4324
4325 return [{
4326 pos: pos,
4327 ids: ids
4328 }];
4329}
4330
4331// Preprocess documents, parse their revisions, assign an id and a
4332// revision for new writes that are missing them, etc
4333function parseDoc(doc, newEdits, dbOpts) {
4334 if (!dbOpts) {
4335 dbOpts = {
4336 deterministic_revs: true
4337 };
4338 }
4339
4340 var nRevNum;
4341 var newRevId;
4342 var revInfo;
4343 var opts = {status: 'available'};
4344 if (doc._deleted) {
4345 opts.deleted = true;
4346 }
4347
4348 if (newEdits) {
4349 if (!doc._id) {
4350 doc._id = uuid();
4351 }
4352 newRevId = rev$$1(doc, dbOpts.deterministic_revs);
4353 if (doc._rev) {
4354 revInfo = parseRevisionInfo(doc._rev);
4355 if (revInfo.error) {
4356 return revInfo;
4357 }
4358 doc._rev_tree = [{
4359 pos: revInfo.prefix,
4360 ids: [revInfo.id, {status: 'missing'}, [[newRevId, opts, []]]]
4361 }];
4362 nRevNum = revInfo.prefix + 1;
4363 } else {
4364 doc._rev_tree = [{
4365 pos: 1,
4366 ids : [newRevId, opts, []]
4367 }];
4368 nRevNum = 1;
4369 }
4370 } else {
4371 if (doc._revisions) {
4372 doc._rev_tree = makeRevTreeFromRevisions(doc._revisions, opts);
4373 nRevNum = doc._revisions.start;
4374 newRevId = doc._revisions.ids[0];
4375 }
4376 if (!doc._rev_tree) {
4377 revInfo = parseRevisionInfo(doc._rev);
4378 if (revInfo.error) {
4379 return revInfo;
4380 }
4381 nRevNum = revInfo.prefix;
4382 newRevId = revInfo.id;
4383 doc._rev_tree = [{
4384 pos: nRevNum,
4385 ids: [newRevId, opts, []]
4386 }];
4387 }
4388 }
4389
4390 invalidIdError(doc._id);
4391
4392 doc._rev = nRevNum + '-' + newRevId;
4393
4394 var result = {metadata : {}, data : {}};
4395 for (var key in doc) {
4396 /* istanbul ignore else */
4397 if (Object.prototype.hasOwnProperty.call(doc, key)) {
4398 var specialKey = key[0] === '_';
4399 if (specialKey && !reservedWords[key]) {
4400 var error = createError(DOC_VALIDATION, key);
4401 error.message = DOC_VALIDATION.message + ': ' + key;
4402 throw error;
4403 } else if (specialKey && !dataWords[key]) {
4404 result.metadata[key.slice(1)] = doc[key];
4405 } else {
4406 result.data[key] = doc[key];
4407 }
4408 }
4409 }
4410 return result;
4411}
4412
4413function thisAtob(str) {
4414 var base64 = new Buffer(str, 'base64');
4415 // Node.js will just skip the characters it can't decode instead of
4416 // throwing an exception
4417 if (base64.toString('base64') !== str) {
4418 throw new Error("attachment is not a valid base64 string");
4419 }
4420 return base64.toString('binary');
4421}
4422
4423function thisBtoa(str) {
4424 return bufferFrom(str, 'binary').toString('base64');
4425}
4426
4427function typedBuffer(binString, buffType, type) {
4428 // buffType is either 'binary' or 'base64'
4429 var buff = bufferFrom(binString, buffType);
4430 buff.type = type; // non-standard, but used for consistency with the browser
4431 return buff;
4432}
4433
4434function b64ToBluffer(b64, type) {
4435 return typedBuffer(b64, 'base64', type);
4436}
4437
4438// From http://stackoverflow.com/questions/14967647/ (continues on next line)
4439
4440function binStringToBluffer(binString, type) {
4441 return typedBuffer(binString, 'binary', type);
4442}
4443
4444// This function is unused in Node
4445
4446function blobToBase64(blobOrBuffer, callback) {
4447 callback(blobOrBuffer.toString('base64'));
4448}
4449
4450// not used in Node, but here for completeness
4451
4452// simplified API. universal browser support is assumed
4453
4454//Can't find original post, but this is close
4455
4456function updateDoc(revLimit, prev, docInfo, results,
4457 i, cb, writeDoc, newEdits) {
4458
4459 if (revExists(prev.rev_tree, docInfo.metadata.rev) && !newEdits) {
4460 results[i] = docInfo;
4461 return cb();
4462 }
4463
4464 // sometimes this is pre-calculated. historically not always
4465 var previousWinningRev = prev.winningRev || winningRev(prev);
4466 var previouslyDeleted = 'deleted' in prev ? prev.deleted :
4467 isDeleted(prev, previousWinningRev);
4468 var deleted = 'deleted' in docInfo.metadata ? docInfo.metadata.deleted :
4469 isDeleted(docInfo.metadata);
4470 var isRoot = /^1-/.test(docInfo.metadata.rev);
4471
4472 if (previouslyDeleted && !deleted && newEdits && isRoot) {
4473 var newDoc = docInfo.data;
4474 newDoc._rev = previousWinningRev;
4475 newDoc._id = docInfo.metadata.id;
4476 docInfo = parseDoc(newDoc, newEdits);
4477 }
4478
4479 var merged = merge(prev.rev_tree, docInfo.metadata.rev_tree[0], revLimit);
4480
4481 var inConflict = newEdits && ((
4482 (previouslyDeleted && deleted && merged.conflicts !== 'new_leaf') ||
4483 (!previouslyDeleted && merged.conflicts !== 'new_leaf') ||
4484 (previouslyDeleted && !deleted && merged.conflicts === 'new_branch')));
4485
4486 if (inConflict) {
4487 var err = createError(REV_CONFLICT);
4488 results[i] = err;
4489 return cb();
4490 }
4491
4492 var newRev = docInfo.metadata.rev;
4493 docInfo.metadata.rev_tree = merged.tree;
4494 docInfo.stemmedRevs = merged.stemmedRevs || [];
4495 /* istanbul ignore else */
4496 if (prev.rev_map) {
4497 docInfo.metadata.rev_map = prev.rev_map; // used only by leveldb
4498 }
4499
4500 // recalculate
4501 var winningRev$$1 = winningRev(docInfo.metadata);
4502 var winningRevIsDeleted = isDeleted(docInfo.metadata, winningRev$$1);
4503
4504 // calculate the total number of documents that were added/removed,
4505 // from the perspective of total_rows/doc_count
4506 var delta = (previouslyDeleted === winningRevIsDeleted) ? 0 :
4507 previouslyDeleted < winningRevIsDeleted ? -1 : 1;
4508
4509 var newRevIsDeleted;
4510 if (newRev === winningRev$$1) {
4511 // if the new rev is the same as the winning rev, we can reuse that value
4512 newRevIsDeleted = winningRevIsDeleted;
4513 } else {
4514 // if they're not the same, then we need to recalculate
4515 newRevIsDeleted = isDeleted(docInfo.metadata, newRev);
4516 }
4517
4518 writeDoc(docInfo, winningRev$$1, winningRevIsDeleted, newRevIsDeleted,
4519 true, delta, i, cb);
4520}
4521
4522function rootIsMissing(docInfo) {
4523 return docInfo.metadata.rev_tree[0].ids[1].status === 'missing';
4524}
4525
4526function processDocs(revLimit, docInfos, api, fetchedDocs, tx, results,
4527 writeDoc, opts, overallCallback) {
4528
4529 // Default to 1000 locally
4530 revLimit = revLimit || 1000;
4531
4532 function insertDoc(docInfo, resultsIdx, callback) {
4533 // Cant insert new deleted documents
4534 var winningRev$$1 = winningRev(docInfo.metadata);
4535 var deleted = isDeleted(docInfo.metadata, winningRev$$1);
4536 if ('was_delete' in opts && deleted) {
4537 results[resultsIdx] = createError(MISSING_DOC, 'deleted');
4538 return callback();
4539 }
4540
4541 // 4712 - detect whether a new document was inserted with a _rev
4542 var inConflict = newEdits && rootIsMissing(docInfo);
4543
4544 if (inConflict) {
4545 var err = createError(REV_CONFLICT);
4546 results[resultsIdx] = err;
4547 return callback();
4548 }
4549
4550 var delta = deleted ? 0 : 1;
4551
4552 writeDoc(docInfo, winningRev$$1, deleted, deleted, false,
4553 delta, resultsIdx, callback);
4554 }
4555
4556 var newEdits = opts.new_edits;
4557 var idsToDocs = new ExportedMap();
4558
4559 var docsDone = 0;
4560 var docsToDo = docInfos.length;
4561
4562 function checkAllDocsDone() {
4563 if (++docsDone === docsToDo && overallCallback) {
4564 overallCallback();
4565 }
4566 }
4567
4568 docInfos.forEach(function (currentDoc, resultsIdx) {
4569
4570 if (currentDoc._id && isLocalId(currentDoc._id)) {
4571 var fun = currentDoc._deleted ? '_removeLocal' : '_putLocal';
4572 api[fun](currentDoc, {ctx: tx}, function (err, res) {
4573 results[resultsIdx] = err || res;
4574 checkAllDocsDone();
4575 });
4576 return;
4577 }
4578
4579 var id = currentDoc.metadata.id;
4580 if (idsToDocs.has(id)) {
4581 docsToDo--; // duplicate
4582 idsToDocs.get(id).push([currentDoc, resultsIdx]);
4583 } else {
4584 idsToDocs.set(id, [[currentDoc, resultsIdx]]);
4585 }
4586 });
4587
4588 // in the case of new_edits, the user can provide multiple docs
4589 // with the same id. these need to be processed sequentially
4590 idsToDocs.forEach(function (docs, id) {
4591 var numDone = 0;
4592
4593 function docWritten() {
4594 if (++numDone < docs.length) {
4595 nextDoc();
4596 } else {
4597 checkAllDocsDone();
4598 }
4599 }
4600 function nextDoc() {
4601 var value = docs[numDone];
4602 var currentDoc = value[0];
4603 var resultsIdx = value[1];
4604
4605 if (fetchedDocs.has(id)) {
4606 updateDoc(revLimit, fetchedDocs.get(id), currentDoc, results,
4607 resultsIdx, docWritten, writeDoc, newEdits);
4608 } else {
4609 // Ensure stemming applies to new writes as well
4610 var merged = merge([], currentDoc.metadata.rev_tree[0], revLimit);
4611 currentDoc.metadata.rev_tree = merged.tree;
4612 currentDoc.stemmedRevs = merged.stemmedRevs || [];
4613 insertDoc(currentDoc, resultsIdx, docWritten);
4614 }
4615 }
4616 nextDoc();
4617 });
4618}
4619
4620function safeJsonParse(str) {
4621 // This try/catch guards against stack overflow errors.
4622 // JSON.parse() is faster than vuvuzela.parse() but vuvuzela
4623 // cannot overflow.
4624 try {
4625 return JSON.parse(str);
4626 } catch (e) {
4627 /* istanbul ignore next */
4628 return vuvuzela.parse(str);
4629 }
4630}
4631
4632function safeJsonStringify(json) {
4633 try {
4634 return JSON.stringify(json);
4635 } catch (e) {
4636 /* istanbul ignore next */
4637 return vuvuzela.stringify(json);
4638 }
4639}
4640
4641function readAsBlobOrBuffer(storedObject, type) {
4642 // In Node, we've stored a buffer
4643 storedObject.type = type; // non-standard, but used for consistency
4644 return storedObject;
4645}
4646
4647// in Node, we store the buffer directly
4648function prepareAttachmentForStorage(attData, cb) {
4649 cb(attData);
4650}
4651
4652function createEmptyBlobOrBuffer(type) {
4653 return typedBuffer('', 'binary', type);
4654}
4655
4656function getCacheFor(transaction, store) {
4657 var prefix = store.prefix()[0];
4658 var cache = transaction._cache;
4659 var subCache = cache.get(prefix);
4660 if (!subCache) {
4661 subCache = new ExportedMap();
4662 cache.set(prefix, subCache);
4663 }
4664 return subCache;
4665}
4666
4667function LevelTransaction() {
4668 this._batch = [];
4669 this._cache = new ExportedMap();
4670}
4671
4672LevelTransaction.prototype.get = function (store, key, callback) {
4673 var cache = getCacheFor(this, store);
4674 var exists = cache.get(key);
4675 if (exists) {
4676 return nextTick(function () {
4677 callback(null, exists);
4678 });
4679 } else if (exists === null) { // deleted marker
4680 /* istanbul ignore next */
4681 return nextTick(function () {
4682 callback({name: 'NotFoundError'});
4683 });
4684 }
4685 store.get(key, function (err, res$$1) {
4686 if (err) {
4687 /* istanbul ignore else */
4688 if (err.name === 'NotFoundError') {
4689 cache.set(key, null);
4690 }
4691 return callback(err);
4692 }
4693 cache.set(key, res$$1);
4694 callback(null, res$$1);
4695 });
4696};
4697
4698LevelTransaction.prototype.batch = function (batch) {
4699 for (var i = 0, len = batch.length; i < len; i++) {
4700 var operation = batch[i];
4701
4702 var cache = getCacheFor(this, operation.prefix);
4703
4704 if (operation.type === 'put') {
4705 cache.set(operation.key, operation.value);
4706 } else {
4707 cache.set(operation.key, null);
4708 }
4709 }
4710 this._batch = this._batch.concat(batch);
4711};
4712
4713LevelTransaction.prototype.execute = function (db, callback) {
4714
4715 var keys = new ExportedSet();
4716 var uniqBatches = [];
4717
4718 // remove duplicates; last one wins
4719 for (var i = this._batch.length - 1; i >= 0; i--) {
4720 var operation = this._batch[i];
4721 var lookupKey = operation.prefix.prefix()[0] + '\xff' + operation.key;
4722 if (keys.has(lookupKey)) {
4723 continue;
4724 }
4725 keys.add(lookupKey);
4726 uniqBatches.push(operation);
4727 }
4728
4729 db.batch(uniqBatches, callback);
4730};
4731
4732var DOC_STORE = 'document-store';
4733var BY_SEQ_STORE = 'by-sequence';
4734var ATTACHMENT_STORE = 'attach-store';
4735var BINARY_STORE = 'attach-binary-store';
4736var LOCAL_STORE = 'local-store';
4737var META_STORE = 'meta-store';
4738
4739// leveldb barks if we try to open a db multiple times
4740// so we cache opened connections here for initstore()
4741var dbStores = new ExportedMap();
4742
4743// store the value of update_seq in the by-sequence store the key name will
4744// never conflict, since the keys in the by-sequence store are integers
4745var UPDATE_SEQ_KEY = '_local_last_update_seq';
4746var DOC_COUNT_KEY = '_local_doc_count';
4747var UUID_KEY = '_local_uuid';
4748
4749var MD5_PREFIX = 'md5-';
4750
4751var safeJsonEncoding = {
4752 encode: safeJsonStringify,
4753 decode: safeJsonParse,
4754 buffer: false,
4755 type: 'cheap-json'
4756};
4757
4758var levelChanges = new Changes();
4759
4760// winningRev and deleted are performance-killers, but
4761// in newer versions of PouchDB, they are cached on the metadata
4762function getWinningRev(metadata) {
4763 return 'winningRev' in metadata ?
4764 metadata.winningRev : winningRev(metadata);
4765}
4766
4767function getIsDeleted(metadata, winningRev$$1) {
4768 return 'deleted' in metadata ?
4769 metadata.deleted : isDeleted(metadata, winningRev$$1);
4770}
4771
4772function fetchAttachment(att, stores, opts) {
4773 var type = att.content_type;
4774 return new Promise(function (resolve, reject) {
4775 stores.binaryStore.get(att.digest, function (err, buffer) {
4776 var data;
4777 if (err) {
4778 /* istanbul ignore if */
4779 if (err.name !== 'NotFoundError') {
4780 return reject(err);
4781 } else {
4782 // empty
4783 if (!opts.binary) {
4784 data = '';
4785 } else {
4786 data = binStringToBluffer('', type);
4787 }
4788 }
4789 } else { // non-empty
4790 if (opts.binary) {
4791 data = readAsBlobOrBuffer(buffer, type);
4792 } else {
4793 data = buffer.toString('base64');
4794 }
4795 }
4796 delete att.stub;
4797 delete att.length;
4798 att.data = data;
4799 resolve();
4800 });
4801 });
4802}
4803
4804function fetchAttachments(results, stores, opts) {
4805 var atts = [];
4806 results.forEach(function (row) {
4807 if (!(row.doc && row.doc._attachments)) {
4808 return;
4809 }
4810 var attNames = Object.keys(row.doc._attachments);
4811 attNames.forEach(function (attName) {
4812 var att = row.doc._attachments[attName];
4813 if (!('data' in att)) {
4814 atts.push(att);
4815 }
4816 });
4817 });
4818
4819 return Promise.all(atts.map(function (att) {
4820 return fetchAttachment(att, stores, opts);
4821 }));
4822}
4823
4824function LevelPouch(opts, callback) {
4825 opts = clone(opts);
4826 var api = this;
4827 var instanceId;
4828 var stores = {};
4829 var revLimit = opts.revs_limit;
4830 var db;
4831 var name = opts.name;
4832 // TODO: this is undocumented and unused probably
4833 /* istanbul ignore else */
4834 if (typeof opts.createIfMissing === 'undefined') {
4835 opts.createIfMissing = true;
4836 }
4837
4838 var leveldown = opts.db;
4839
4840 var dbStore;
4841 var leveldownName = functionName(leveldown);
4842 if (dbStores.has(leveldownName)) {
4843 dbStore = dbStores.get(leveldownName);
4844 } else {
4845 dbStore = new ExportedMap();
4846 dbStores.set(leveldownName, dbStore);
4847 }
4848 if (dbStore.has(name)) {
4849 db = dbStore.get(name);
4850 afterDBCreated();
4851 } else {
4852 dbStore.set(name, sublevelPouch(levelup(leveldown(name), opts, function (err) {
4853 /* istanbul ignore if */
4854 if (err) {
4855 dbStore.delete(name);
4856 return callback(err);
4857 }
4858 db = dbStore.get(name);
4859 db._docCount = -1;
4860 db._queue = new Deque();
4861 /* istanbul ignore else */
4862 if (typeof opts.migrate === 'object') { // migration for leveldown
4863 opts.migrate.doMigrationOne(name, db, afterDBCreated);
4864 } else {
4865 afterDBCreated();
4866 }
4867 })));
4868 }
4869
4870 function afterDBCreated() {
4871 stores.docStore = db.sublevel(DOC_STORE, {valueEncoding: safeJsonEncoding});
4872 stores.bySeqStore = db.sublevel(BY_SEQ_STORE, {valueEncoding: 'json'});
4873 stores.attachmentStore =
4874 db.sublevel(ATTACHMENT_STORE, {valueEncoding: 'json'});
4875 stores.binaryStore = db.sublevel(BINARY_STORE, {valueEncoding: 'binary'});
4876 stores.localStore = db.sublevel(LOCAL_STORE, {valueEncoding: 'json'});
4877 stores.metaStore = db.sublevel(META_STORE, {valueEncoding: 'json'});
4878 /* istanbul ignore else */
4879 if (typeof opts.migrate === 'object') { // migration for leveldown
4880 opts.migrate.doMigrationTwo(db, stores, afterLastMigration);
4881 } else {
4882 afterLastMigration();
4883 }
4884 }
4885
4886 function afterLastMigration() {
4887 stores.metaStore.get(UPDATE_SEQ_KEY, function (err, value) {
4888 if (typeof db._updateSeq === 'undefined') {
4889 db._updateSeq = value || 0;
4890 }
4891 stores.metaStore.get(DOC_COUNT_KEY, function (err, value) {
4892 db._docCount = !err ? value : 0;
4893 stores.metaStore.get(UUID_KEY, function (err, value) {
4894 instanceId = !err ? value : uuid();
4895 stores.metaStore.put(UUID_KEY, instanceId, function () {
4896 nextTick(function () {
4897 callback(null, api);
4898 });
4899 });
4900 });
4901 });
4902 });
4903 }
4904
4905 function countDocs(callback) {
4906 /* istanbul ignore if */
4907 if (db.isClosed()) {
4908 return callback(new Error('database is closed'));
4909 }
4910 return callback(null, db._docCount); // use cached value
4911 }
4912
4913 api._remote = false;
4914 /* istanbul ignore next */
4915 api.type = function () {
4916 return 'leveldb';
4917 };
4918
4919 api._id = function (callback) {
4920 callback(null, instanceId);
4921 };
4922
4923 api._info = function (callback) {
4924 var res$$1 = {
4925 doc_count: db._docCount,
4926 update_seq: db._updateSeq,
4927 backend_adapter: functionName(leveldown)
4928 };
4929 return nextTick(function () {
4930 callback(null, res$$1);
4931 });
4932 };
4933
4934 function tryCode(fun, args) {
4935 try {
4936 fun.apply(null, args);
4937 } catch (err) {
4938 args[args.length - 1](err);
4939 }
4940 }
4941
4942 function executeNext() {
4943 var firstTask = db._queue.peekFront();
4944
4945 if (firstTask.type === 'read') {
4946 runReadOperation(firstTask);
4947 } else { // write, only do one at a time
4948 runWriteOperation(firstTask);
4949 }
4950 }
4951
4952 function runReadOperation(firstTask) {
4953 // do multiple reads at once simultaneously, because it's safe
4954
4955 var readTasks = [firstTask];
4956 var i = 1;
4957 var nextTask = db._queue.get(i);
4958 while (typeof nextTask !== 'undefined' && nextTask.type === 'read') {
4959 readTasks.push(nextTask);
4960 i++;
4961 nextTask = db._queue.get(i);
4962 }
4963
4964 var numDone = 0;
4965
4966 readTasks.forEach(function (readTask) {
4967 var args = readTask.args;
4968 var callback = args[args.length - 1];
4969 args[args.length - 1] = getArguments(function (cbArgs) {
4970 callback.apply(null, cbArgs);
4971 if (++numDone === readTasks.length) {
4972 nextTick(function () {
4973 // all read tasks have finished
4974 readTasks.forEach(function () {
4975 db._queue.shift();
4976 });
4977 if (db._queue.length) {
4978 executeNext();
4979 }
4980 });
4981 }
4982 });
4983 tryCode(readTask.fun, args);
4984 });
4985 }
4986
4987 function runWriteOperation(firstTask) {
4988 var args = firstTask.args;
4989 var callback = args[args.length - 1];
4990 args[args.length - 1] = getArguments(function (cbArgs) {
4991 callback.apply(null, cbArgs);
4992 nextTick(function () {
4993 db._queue.shift();
4994 if (db._queue.length) {
4995 executeNext();
4996 }
4997 });
4998 });
4999 tryCode(firstTask.fun, args);
5000 }
5001
5002 // all read/write operations to the database are done in a queue,
5003 // similar to how websql/idb works. this avoids problems such
5004 // as e.g. compaction needing to have a lock on the database while
5005 // it updates stuff. in the future we can revisit this.
5006 function writeLock(fun) {
5007 return getArguments(function (args) {
5008 db._queue.push({
5009 fun: fun,
5010 args: args,
5011 type: 'write'
5012 });
5013
5014 if (db._queue.length === 1) {
5015 nextTick(executeNext);
5016 }
5017 });
5018 }
5019
5020 // same as the writelock, but multiple can run at once
5021 function readLock(fun) {
5022 return getArguments(function (args) {
5023 db._queue.push({
5024 fun: fun,
5025 args: args,
5026 type: 'read'
5027 });
5028
5029 if (db._queue.length === 1) {
5030 nextTick(executeNext);
5031 }
5032 });
5033 }
5034
5035 function formatSeq(n) {
5036 return ('0000000000000000' + n).slice(-16);
5037 }
5038
5039 function parseSeq(s) {
5040 return parseInt(s, 10);
5041 }
5042
5043 api._get = readLock(function (id, opts, callback) {
5044 opts = clone(opts);
5045
5046 stores.docStore.get(id, function (err, metadata) {
5047
5048 if (err || !metadata) {
5049 return callback(createError(MISSING_DOC, 'missing'));
5050 }
5051
5052 var rev;
5053 if (!opts.rev) {
5054 rev = getWinningRev(metadata);
5055 var deleted = getIsDeleted(metadata, rev);
5056 if (deleted) {
5057 return callback(createError(MISSING_DOC, "deleted"));
5058 }
5059 } else {
5060 rev = opts.latest ? latest(opts.rev, metadata) : opts.rev;
5061 }
5062
5063 var seq = metadata.rev_map[rev];
5064
5065 stores.bySeqStore.get(formatSeq(seq), function (err, doc) {
5066 if (!doc) {
5067 return callback(createError(MISSING_DOC));
5068 }
5069 /* istanbul ignore if */
5070 if ('_id' in doc && doc._id !== metadata.id) {
5071 // this failing implies something very wrong
5072 return callback(new Error('wrong doc returned'));
5073 }
5074 doc._id = metadata.id;
5075 if ('_rev' in doc) {
5076 /* istanbul ignore if */
5077 if (doc._rev !== rev) {
5078 // this failing implies something very wrong
5079 return callback(new Error('wrong doc returned'));
5080 }
5081 } else {
5082 // we didn't always store this
5083 doc._rev = rev;
5084 }
5085 return callback(null, {doc: doc, metadata: metadata});
5086 });
5087 });
5088 });
5089
5090 // not technically part of the spec, but if putAttachment has its own
5091 // method...
5092 api._getAttachment = function (docId, attachId, attachment, opts, callback) {
5093 var digest = attachment.digest;
5094 var type = attachment.content_type;
5095
5096 stores.binaryStore.get(digest, function (err, attach) {
5097 if (err) {
5098 /* istanbul ignore if */
5099 if (err.name !== 'NotFoundError') {
5100 return callback(err);
5101 }
5102 // Empty attachment
5103 return callback(null, opts.binary ? createEmptyBlobOrBuffer(type) : '');
5104 }
5105
5106 if (opts.binary) {
5107 callback(null, readAsBlobOrBuffer(attach, type));
5108 } else {
5109 callback(null, attach.toString('base64'));
5110 }
5111 });
5112 };
5113
5114 api._bulkDocs = writeLock(function (req, opts, callback) {
5115 var newEdits = opts.new_edits;
5116 var results = new Array(req.docs.length);
5117 var fetchedDocs = new ExportedMap();
5118 var stemmedRevs = new ExportedMap();
5119
5120 var txn = new LevelTransaction();
5121 var docCountDelta = 0;
5122 var newUpdateSeq = db._updateSeq;
5123
5124 // parse the docs and give each a sequence number
5125 var userDocs = req.docs;
5126 var docInfos = userDocs.map(function (doc) {
5127 if (doc._id && isLocalId(doc._id)) {
5128 return doc;
5129 }
5130 var newDoc = parseDoc(doc, newEdits, api.__opts);
5131
5132 if (newDoc.metadata && !newDoc.metadata.rev_map) {
5133 newDoc.metadata.rev_map = {};
5134 }
5135
5136 return newDoc;
5137 });
5138 var infoErrors = docInfos.filter(function (doc) {
5139 return doc.error;
5140 });
5141
5142 if (infoErrors.length) {
5143 return callback(infoErrors[0]);
5144 }
5145
5146 // verify any stub attachments as a precondition test
5147
5148 function verifyAttachment(digest, callback) {
5149 txn.get(stores.attachmentStore, digest, function (levelErr) {
5150 if (levelErr) {
5151 var err = createError(MISSING_STUB,
5152 'unknown stub attachment with digest ' +
5153 digest);
5154 callback(err);
5155 } else {
5156 callback();
5157 }
5158 });
5159 }
5160
5161 function verifyAttachments(finish) {
5162 var digests = [];
5163 userDocs.forEach(function (doc) {
5164 if (doc && doc._attachments) {
5165 Object.keys(doc._attachments).forEach(function (filename) {
5166 var att = doc._attachments[filename];
5167 if (att.stub) {
5168 digests.push(att.digest);
5169 }
5170 });
5171 }
5172 });
5173 if (!digests.length) {
5174 return finish();
5175 }
5176 var numDone = 0;
5177 var err;
5178
5179 digests.forEach(function (digest) {
5180 verifyAttachment(digest, function (attErr) {
5181 if (attErr && !err) {
5182 err = attErr;
5183 }
5184
5185 if (++numDone === digests.length) {
5186 finish(err);
5187 }
5188 });
5189 });
5190 }
5191
5192 function fetchExistingDocs(finish) {
5193 var numDone = 0;
5194 var overallErr;
5195 function checkDone() {
5196 if (++numDone === userDocs.length) {
5197 return finish(overallErr);
5198 }
5199 }
5200
5201 userDocs.forEach(function (doc) {
5202 if (doc._id && isLocalId(doc._id)) {
5203 // skip local docs
5204 return checkDone();
5205 }
5206 txn.get(stores.docStore, doc._id, function (err, info) {
5207 if (err) {
5208 /* istanbul ignore if */
5209 if (err.name !== 'NotFoundError') {
5210 overallErr = err;
5211 }
5212 } else {
5213 fetchedDocs.set(doc._id, info);
5214 }
5215 checkDone();
5216 });
5217 });
5218 }
5219
5220 function compact(revsMap, callback) {
5221 var promise = Promise.resolve();
5222 revsMap.forEach(function (revs, docId) {
5223 // TODO: parallelize, for now need to be sequential to
5224 // pass orphaned attachment tests
5225 promise = promise.then(function () {
5226 return new Promise(function (resolve, reject) {
5227 api._doCompactionNoLock(docId, revs, {ctx: txn}, function (err) {
5228 /* istanbul ignore if */
5229 if (err) {
5230 return reject(err);
5231 }
5232 resolve();
5233 });
5234 });
5235 });
5236 });
5237
5238 promise.then(function () {
5239 callback();
5240 }, callback);
5241 }
5242
5243 function autoCompact(callback) {
5244 var revsMap = new ExportedMap();
5245 fetchedDocs.forEach(function (metadata, docId) {
5246 revsMap.set(docId, compactTree(metadata));
5247 });
5248 compact(revsMap, callback);
5249 }
5250
5251 function finish() {
5252 compact(stemmedRevs, function (error) {
5253 /* istanbul ignore if */
5254 if (error) {
5255 complete(error);
5256 }
5257 if (api.auto_compaction) {
5258 return autoCompact(complete);
5259 }
5260 complete();
5261 });
5262 }
5263
5264 function writeDoc(docInfo, winningRev$$1, winningRevIsDeleted, newRevIsDeleted,
5265 isUpdate, delta, resultsIdx, callback2) {
5266 docCountDelta += delta;
5267
5268 var err = null;
5269 var recv = 0;
5270
5271 docInfo.metadata.winningRev = winningRev$$1;
5272 docInfo.metadata.deleted = winningRevIsDeleted;
5273
5274 docInfo.data._id = docInfo.metadata.id;
5275 docInfo.data._rev = docInfo.metadata.rev;
5276
5277 if (newRevIsDeleted) {
5278 docInfo.data._deleted = true;
5279 }
5280
5281 if (docInfo.stemmedRevs.length) {
5282 stemmedRevs.set(docInfo.metadata.id, docInfo.stemmedRevs);
5283 }
5284
5285 var attachments = docInfo.data._attachments ?
5286 Object.keys(docInfo.data._attachments) :
5287 [];
5288
5289 function attachmentSaved(attachmentErr) {
5290 recv++;
5291 if (!err) {
5292 /* istanbul ignore if */
5293 if (attachmentErr) {
5294 err = attachmentErr;
5295 callback2(err);
5296 } else if (recv === attachments.length) {
5297 finish();
5298 }
5299 }
5300 }
5301
5302 function onMD5Load(doc, key, data, attachmentSaved) {
5303 return function (result) {
5304 saveAttachment(doc, MD5_PREFIX + result, key, data, attachmentSaved);
5305 };
5306 }
5307
5308 function doMD5(doc, key, attachmentSaved) {
5309 return function (data) {
5310 binaryMd5(data, onMD5Load(doc, key, data, attachmentSaved));
5311 };
5312 }
5313
5314 for (var i = 0; i < attachments.length; i++) {
5315 var key = attachments[i];
5316 var att = docInfo.data._attachments[key];
5317
5318 if (att.stub) {
5319 // still need to update the refs mapping
5320 var id = docInfo.data._id;
5321 var rev = docInfo.data._rev;
5322 saveAttachmentRefs(id, rev, att.digest, attachmentSaved);
5323 continue;
5324 }
5325 var data;
5326 if (typeof att.data === 'string') {
5327 // input is assumed to be a base64 string
5328 try {
5329 data = thisAtob(att.data);
5330 } catch (e) {
5331 callback(createError(BAD_ARG,
5332 'Attachment is not a valid base64 string'));
5333 return;
5334 }
5335 doMD5(docInfo, key, attachmentSaved)(data);
5336 } else {
5337 prepareAttachmentForStorage(att.data,
5338 doMD5(docInfo, key, attachmentSaved));
5339 }
5340 }
5341
5342 function finish() {
5343 var seq = docInfo.metadata.rev_map[docInfo.metadata.rev];
5344 /* istanbul ignore if */
5345 if (seq) {
5346 // check that there aren't any existing revisions with the same
5347 // revision id, else we shouldn't do anything
5348 return callback2();
5349 }
5350 seq = ++newUpdateSeq;
5351 docInfo.metadata.rev_map[docInfo.metadata.rev] =
5352 docInfo.metadata.seq = seq;
5353 var seqKey = formatSeq(seq);
5354 var batch = [{
5355 key: seqKey,
5356 value: docInfo.data,
5357 prefix: stores.bySeqStore,
5358 type: 'put'
5359 }, {
5360 key: docInfo.metadata.id,
5361 value: docInfo.metadata,
5362 prefix: stores.docStore,
5363 type: 'put'
5364 }];
5365 txn.batch(batch);
5366 results[resultsIdx] = {
5367 ok: true,
5368 id: docInfo.metadata.id,
5369 rev: docInfo.metadata.rev
5370 };
5371 fetchedDocs.set(docInfo.metadata.id, docInfo.metadata);
5372 callback2();
5373 }
5374
5375 if (!attachments.length) {
5376 finish();
5377 }
5378 }
5379
5380 // attachments are queued per-digest, otherwise the refs could be
5381 // overwritten by concurrent writes in the same bulkDocs session
5382 var attachmentQueues = {};
5383
5384 function saveAttachmentRefs(id, rev, digest, callback) {
5385
5386 function fetchAtt() {
5387 return new Promise(function (resolve, reject) {
5388 txn.get(stores.attachmentStore, digest, function (err, oldAtt) {
5389 /* istanbul ignore if */
5390 if (err && err.name !== 'NotFoundError') {
5391 return reject(err);
5392 }
5393 resolve(oldAtt);
5394 });
5395 });
5396 }
5397
5398 function saveAtt(oldAtt) {
5399 var ref = [id, rev].join('@');
5400 var newAtt = {};
5401
5402 if (oldAtt) {
5403 if (oldAtt.refs) {
5404 // only update references if this attachment already has them
5405 // since we cannot migrate old style attachments here without
5406 // doing a full db scan for references
5407 newAtt.refs = oldAtt.refs;
5408 newAtt.refs[ref] = true;
5409 }
5410 } else {
5411 newAtt.refs = {};
5412 newAtt.refs[ref] = true;
5413 }
5414
5415 return new Promise(function (resolve) {
5416 txn.batch([{
5417 type: 'put',
5418 prefix: stores.attachmentStore,
5419 key: digest,
5420 value: newAtt
5421 }]);
5422 resolve(!oldAtt);
5423 });
5424 }
5425
5426 // put attachments in a per-digest queue, to avoid two docs with the same
5427 // attachment overwriting each other
5428 var queue = attachmentQueues[digest] || Promise.resolve();
5429 attachmentQueues[digest] = queue.then(function () {
5430 return fetchAtt().then(saveAtt).then(function (isNewAttachment) {
5431 callback(null, isNewAttachment);
5432 }, callback);
5433 });
5434 }
5435
5436 function saveAttachment(docInfo, digest, key, data, callback) {
5437 var att = docInfo.data._attachments[key];
5438 delete att.data;
5439 att.digest = digest;
5440 att.length = data.length;
5441 var id = docInfo.metadata.id;
5442 var rev = docInfo.metadata.rev;
5443 att.revpos = parseInt(rev, 10);
5444
5445 saveAttachmentRefs(id, rev, digest, function (err, isNewAttachment) {
5446 /* istanbul ignore if */
5447 if (err) {
5448 return callback(err);
5449 }
5450 // do not try to store empty attachments
5451 if (data.length === 0) {
5452 return callback(err);
5453 }
5454 if (!isNewAttachment) {
5455 // small optimization - don't bother writing it again
5456 return callback(err);
5457 }
5458 txn.batch([{
5459 type: 'put',
5460 prefix: stores.binaryStore,
5461 key: digest,
5462 value: bufferFrom(data, 'binary')
5463 }]);
5464 callback();
5465 });
5466 }
5467
5468 function complete(err) {
5469 /* istanbul ignore if */
5470 if (err) {
5471 return nextTick(function () {
5472 callback(err);
5473 });
5474 }
5475 txn.batch([
5476 {
5477 prefix: stores.metaStore,
5478 type: 'put',
5479 key: UPDATE_SEQ_KEY,
5480 value: newUpdateSeq
5481 },
5482 {
5483 prefix: stores.metaStore,
5484 type: 'put',
5485 key: DOC_COUNT_KEY,
5486 value: db._docCount + docCountDelta
5487 }
5488 ]);
5489 txn.execute(db, function (err) {
5490 /* istanbul ignore if */
5491 if (err) {
5492 return callback(err);
5493 }
5494 db._docCount += docCountDelta;
5495 db._updateSeq = newUpdateSeq;
5496 levelChanges.notify(name);
5497 nextTick(function () {
5498 callback(null, results);
5499 });
5500 });
5501 }
5502
5503 if (!docInfos.length) {
5504 return callback(null, []);
5505 }
5506
5507 verifyAttachments(function (err) {
5508 if (err) {
5509 return callback(err);
5510 }
5511 fetchExistingDocs(function (err) {
5512 /* istanbul ignore if */
5513 if (err) {
5514 return callback(err);
5515 }
5516 processDocs(revLimit, docInfos, api, fetchedDocs, txn, results,
5517 writeDoc, opts, finish);
5518 });
5519 });
5520 });
5521 api._allDocs = function (opts, callback) {
5522 if ('keys' in opts) {
5523 return allDocsKeysQuery(this, opts);
5524 }
5525 return readLock(function (opts, callback) {
5526 opts = clone(opts);
5527 countDocs(function (err, docCount) {
5528 /* istanbul ignore if */
5529 if (err) {
5530 return callback(err);
5531 }
5532 var readstreamOpts = {};
5533 var skip = opts.skip || 0;
5534 if (opts.startkey) {
5535 readstreamOpts.gte = opts.startkey;
5536 }
5537 if (opts.endkey) {
5538 readstreamOpts.lte = opts.endkey;
5539 }
5540 if (opts.key) {
5541 readstreamOpts.gte = readstreamOpts.lte = opts.key;
5542 }
5543 if (opts.descending) {
5544 readstreamOpts.reverse = true;
5545 // switch start and ends
5546 var tmp = readstreamOpts.lte;
5547 readstreamOpts.lte = readstreamOpts.gte;
5548 readstreamOpts.gte = tmp;
5549 }
5550 var limit;
5551 if (typeof opts.limit === 'number') {
5552 limit = opts.limit;
5553 }
5554 if (limit === 0 ||
5555 ('gte' in readstreamOpts && 'lte' in readstreamOpts &&
5556 readstreamOpts.gte > readstreamOpts.lte)) {
5557 // should return 0 results when start is greater than end.
5558 // normally level would "fix" this for us by reversing the order,
5559 // so short-circuit instead
5560 var returnVal = {
5561 total_rows: docCount,
5562 offset: opts.skip,
5563 rows: []
5564 };
5565 /* istanbul ignore if */
5566 if (opts.update_seq) {
5567 returnVal.update_seq = db._updateSeq;
5568 }
5569 return callback(null, returnVal);
5570 }
5571 var results = [];
5572 var docstream = stores.docStore.readStream(readstreamOpts);
5573
5574 var throughStream = obj(function (entry, _, next) {
5575 var metadata = entry.value;
5576 // winningRev and deleted are performance-killers, but
5577 // in newer versions of PouchDB, they are cached on the metadata
5578 var winningRev$$1 = getWinningRev(metadata);
5579 var deleted = getIsDeleted(metadata, winningRev$$1);
5580 if (!deleted) {
5581 if (skip-- > 0) {
5582 next();
5583 return;
5584 } else if (typeof limit === 'number' && limit-- <= 0) {
5585 docstream.unpipe();
5586 docstream.destroy();
5587 next();
5588 return;
5589 }
5590 } else if (opts.deleted !== 'ok') {
5591 next();
5592 return;
5593 }
5594 function allDocsInner(data) {
5595 var doc = {
5596 id: metadata.id,
5597 key: metadata.id,
5598 value: {
5599 rev: winningRev$$1
5600 }
5601 };
5602 if (opts.include_docs) {
5603 doc.doc = data;
5604 doc.doc._rev = doc.value.rev;
5605 if (opts.conflicts) {
5606 var conflicts = collectConflicts(metadata);
5607 if (conflicts.length) {
5608 doc.doc._conflicts = conflicts;
5609 }
5610 }
5611 for (var att in doc.doc._attachments) {
5612 if (doc.doc._attachments.hasOwnProperty(att)) {
5613 doc.doc._attachments[att].stub = true;
5614 }
5615 }
5616 }
5617 if (opts.inclusive_end === false && metadata.id === opts.endkey) {
5618 return next();
5619 } else if (deleted) {
5620 if (opts.deleted === 'ok') {
5621 doc.value.deleted = true;
5622 doc.doc = null;
5623 } else {
5624 /* istanbul ignore next */
5625 return next();
5626 }
5627 }
5628 results.push(doc);
5629 next();
5630 }
5631 if (opts.include_docs) {
5632 var seq = metadata.rev_map[winningRev$$1];
5633 stores.bySeqStore.get(formatSeq(seq), function (err, data) {
5634 allDocsInner(data);
5635 });
5636 }
5637 else {
5638 allDocsInner();
5639 }
5640 }, function (next) {
5641 Promise.resolve().then(function () {
5642 if (opts.include_docs && opts.attachments) {
5643 return fetchAttachments(results, stores, opts);
5644 }
5645 }).then(function () {
5646 var returnVal = {
5647 total_rows: docCount,
5648 offset: opts.skip,
5649 rows: results
5650 };
5651
5652 /* istanbul ignore if */
5653 if (opts.update_seq) {
5654 returnVal.update_seq = db._updateSeq;
5655 }
5656 callback(null, returnVal);
5657 }, callback);
5658 next();
5659 }).on('unpipe', function () {
5660 throughStream.end();
5661 });
5662
5663 docstream.on('error', callback);
5664
5665 docstream.pipe(throughStream);
5666 });
5667 })(opts, callback);
5668 };
5669
5670 api._changes = function (opts) {
5671 opts = clone(opts);
5672
5673 if (opts.continuous) {
5674 var id = name + ':' + uuid();
5675 levelChanges.addListener(name, id, api, opts);
5676 levelChanges.notify(name);
5677 return {
5678 cancel: function () {
5679 levelChanges.removeListener(name, id);
5680 }
5681 };
5682 }
5683
5684 var descending = opts.descending;
5685 var results = [];
5686 var lastSeq = opts.since || 0;
5687 var called = 0;
5688 var streamOpts = {
5689 reverse: descending
5690 };
5691 var limit;
5692 if ('limit' in opts && opts.limit > 0) {
5693 limit = opts.limit;
5694 }
5695 if (!streamOpts.reverse) {
5696 streamOpts.start = formatSeq(opts.since || 0);
5697 }
5698
5699 var docIds = opts.doc_ids && new ExportedSet(opts.doc_ids);
5700 var filter = filterChange(opts);
5701 var docIdsToMetadata = new ExportedMap();
5702
5703 function complete() {
5704 opts.done = true;
5705 if (opts.return_docs && opts.limit) {
5706 /* istanbul ignore if */
5707 if (opts.limit < results.length) {
5708 results.length = opts.limit;
5709 }
5710 }
5711 changeStream.unpipe(throughStream);
5712 changeStream.destroy();
5713 if (!opts.continuous && !opts.cancelled) {
5714 if (opts.include_docs && opts.attachments && opts.return_docs) {
5715 fetchAttachments(results, stores, opts).then(function () {
5716 opts.complete(null, {results: results, last_seq: lastSeq});
5717 });
5718 } else {
5719 opts.complete(null, {results: results, last_seq: lastSeq});
5720 }
5721 }
5722 }
5723 var changeStream = stores.bySeqStore.readStream(streamOpts);
5724 var throughStream = obj(function (data, _, next) {
5725 if (limit && called >= limit) {
5726 complete();
5727 return next();
5728 }
5729 if (opts.cancelled || opts.done) {
5730 return next();
5731 }
5732
5733 var seq = parseSeq(data.key);
5734 var doc = data.value;
5735
5736 if (seq === opts.since && !descending) {
5737 // couchdb ignores `since` if descending=true
5738 return next();
5739 }
5740
5741 if (docIds && !docIds.has(doc._id)) {
5742 return next();
5743 }
5744
5745 var metadata;
5746
5747 function onGetMetadata(metadata) {
5748 var winningRev$$1 = getWinningRev(metadata);
5749
5750 function onGetWinningDoc(winningDoc) {
5751
5752 var change = opts.processChange(winningDoc, metadata, opts);
5753 change.seq = metadata.seq;
5754
5755 var filtered = filter(change);
5756 if (typeof filtered === 'object') {
5757 return opts.complete(filtered);
5758 }
5759
5760 if (filtered) {
5761 called++;
5762
5763 if (opts.attachments && opts.include_docs) {
5764 // fetch attachment immediately for the benefit
5765 // of live listeners
5766 fetchAttachments([change], stores, opts).then(function () {
5767 opts.onChange(change);
5768 });
5769 } else {
5770 opts.onChange(change);
5771 }
5772
5773 if (opts.return_docs) {
5774 results.push(change);
5775 }
5776 }
5777 next();
5778 }
5779
5780 if (metadata.seq !== seq) {
5781 // some other seq is later
5782 return next();
5783 }
5784
5785 lastSeq = seq;
5786
5787 if (winningRev$$1 === doc._rev) {
5788 return onGetWinningDoc(doc);
5789 }
5790
5791 // fetch the winner
5792
5793 var winningSeq = metadata.rev_map[winningRev$$1];
5794
5795 stores.bySeqStore.get(formatSeq(winningSeq), function (err, doc) {
5796 onGetWinningDoc(doc);
5797 });
5798 }
5799
5800 metadata = docIdsToMetadata.get(doc._id);
5801 if (metadata) { // cached
5802 return onGetMetadata(metadata);
5803 }
5804 // metadata not cached, have to go fetch it
5805 stores.docStore.get(doc._id, function (err, metadata) {
5806 /* istanbul ignore if */
5807 if (opts.cancelled || opts.done || db.isClosed() ||
5808 isLocalId(metadata.id)) {
5809 return next();
5810 }
5811 docIdsToMetadata.set(doc._id, metadata);
5812 onGetMetadata(metadata);
5813 });
5814 }, function (next) {
5815 if (opts.cancelled) {
5816 return next();
5817 }
5818 if (opts.return_docs && opts.limit) {
5819 /* istanbul ignore if */
5820 if (opts.limit < results.length) {
5821 results.length = opts.limit;
5822 }
5823 }
5824
5825 next();
5826 }).on('unpipe', function () {
5827 throughStream.end();
5828 complete();
5829 });
5830 changeStream.pipe(throughStream);
5831 return {
5832 cancel: function () {
5833 opts.cancelled = true;
5834 complete();
5835 }
5836 };
5837 };
5838
5839 api._close = function (callback) {
5840 /* istanbul ignore if */
5841 if (db.isClosed()) {
5842 return callback(createError(NOT_OPEN));
5843 }
5844 db.close(function (err) {
5845 /* istanbul ignore if */
5846 if (err) {
5847 callback(err);
5848 } else {
5849 dbStore.delete(name);
5850 callback();
5851 }
5852 });
5853 };
5854
5855 api._getRevisionTree = function (docId, callback) {
5856 stores.docStore.get(docId, function (err, metadata) {
5857 if (err) {
5858 callback(createError(MISSING_DOC));
5859 } else {
5860 callback(null, metadata.rev_tree);
5861 }
5862 });
5863 };
5864
5865 api._doCompaction = writeLock(function (docId, revs, opts, callback) {
5866 api._doCompactionNoLock(docId, revs, opts, callback);
5867 });
5868
5869 // the NoLock version is for use by bulkDocs
5870 api._doCompactionNoLock = function (docId, revs, opts, callback) {
5871 if (typeof opts === 'function') {
5872 callback = opts;
5873 opts = {};
5874 }
5875
5876 if (!revs.length) {
5877 return callback();
5878 }
5879 var txn = opts.ctx || new LevelTransaction();
5880
5881 txn.get(stores.docStore, docId, function (err, metadata) {
5882 /* istanbul ignore if */
5883 if (err) {
5884 return callback(err);
5885 }
5886 var seqs = revs.map(function (rev) {
5887 var seq = metadata.rev_map[rev];
5888 delete metadata.rev_map[rev];
5889 return seq;
5890 });
5891 traverseRevTree(metadata.rev_tree, function (isLeaf, pos,
5892 revHash, ctx, opts) {
5893 var rev = pos + '-' + revHash;
5894 if (revs.indexOf(rev) !== -1) {
5895 opts.status = 'missing';
5896 }
5897 });
5898
5899 var batch = [];
5900 batch.push({
5901 key: metadata.id,
5902 value: metadata,
5903 type: 'put',
5904 prefix: stores.docStore
5905 });
5906
5907 var digestMap = {};
5908 var numDone = 0;
5909 var overallErr;
5910 function checkDone(err) {
5911 /* istanbul ignore if */
5912 if (err) {
5913 overallErr = err;
5914 }
5915 if (++numDone === revs.length) { // done
5916 /* istanbul ignore if */
5917 if (overallErr) {
5918 return callback(overallErr);
5919 }
5920 deleteOrphanedAttachments();
5921 }
5922 }
5923
5924 function finish(err) {
5925 /* istanbul ignore if */
5926 if (err) {
5927 return callback(err);
5928 }
5929 txn.batch(batch);
5930 if (opts.ctx) {
5931 // don't execute immediately
5932 return callback();
5933 }
5934 txn.execute(db, callback);
5935 }
5936
5937 function deleteOrphanedAttachments() {
5938 var possiblyOrphanedAttachments = Object.keys(digestMap);
5939 if (!possiblyOrphanedAttachments.length) {
5940 return finish();
5941 }
5942 var numDone = 0;
5943 var overallErr;
5944 function checkDone(err) {
5945 /* istanbul ignore if */
5946 if (err) {
5947 overallErr = err;
5948 }
5949 if (++numDone === possiblyOrphanedAttachments.length) {
5950 finish(overallErr);
5951 }
5952 }
5953 var refsToDelete = new ExportedMap();
5954 revs.forEach(function (rev) {
5955 refsToDelete.set(docId + '@' + rev, true);
5956 });
5957 possiblyOrphanedAttachments.forEach(function (digest) {
5958 txn.get(stores.attachmentStore, digest, function (err, attData) {
5959 /* istanbul ignore if */
5960 if (err) {
5961 if (err.name === 'NotFoundError') {
5962 return checkDone();
5963 } else {
5964 return checkDone(err);
5965 }
5966 }
5967 var refs = Object.keys(attData.refs || {}).filter(function (ref) {
5968 return !refsToDelete.has(ref);
5969 });
5970 var newRefs = {};
5971 refs.forEach(function (ref) {
5972 newRefs[ref] = true;
5973 });
5974 if (refs.length) { // not orphaned
5975 batch.push({
5976 key: digest,
5977 type: 'put',
5978 value: {refs: newRefs},
5979 prefix: stores.attachmentStore
5980 });
5981 } else { // orphaned, can safely delete
5982 batch = batch.concat([{
5983 key: digest,
5984 type: 'del',
5985 prefix: stores.attachmentStore
5986 }, {
5987 key: digest,
5988 type: 'del',
5989 prefix: stores.binaryStore
5990 }]);
5991 }
5992 checkDone();
5993 });
5994 });
5995 }
5996
5997 seqs.forEach(function (seq) {
5998 batch.push({
5999 key: formatSeq(seq),
6000 type: 'del',
6001 prefix: stores.bySeqStore
6002 });
6003 txn.get(stores.bySeqStore, formatSeq(seq), function (err, doc) {
6004 /* istanbul ignore if */
6005 if (err) {
6006 if (err.name === 'NotFoundError') {
6007 return checkDone();
6008 } else {
6009 return checkDone(err);
6010 }
6011 }
6012 var atts = Object.keys(doc._attachments || {});
6013 atts.forEach(function (attName) {
6014 var digest = doc._attachments[attName].digest;
6015 digestMap[digest] = true;
6016 });
6017 checkDone();
6018 });
6019 });
6020 });
6021 };
6022
6023 api._getLocal = function (id, callback) {
6024 stores.localStore.get(id, function (err, doc) {
6025 if (err) {
6026 callback(createError(MISSING_DOC));
6027 } else {
6028 callback(null, doc);
6029 }
6030 });
6031 };
6032
6033 api._putLocal = function (doc, opts, callback) {
6034 if (typeof opts === 'function') {
6035 callback = opts;
6036 opts = {};
6037 }
6038 if (opts.ctx) {
6039 api._putLocalNoLock(doc, opts, callback);
6040 } else {
6041 api._putLocalWithLock(doc, opts, callback);
6042 }
6043 };
6044
6045 api._putLocalWithLock = writeLock(function (doc, opts, callback) {
6046 api._putLocalNoLock(doc, opts, callback);
6047 });
6048
6049 // the NoLock version is for use by bulkDocs
6050 api._putLocalNoLock = function (doc, opts, callback) {
6051 delete doc._revisions; // ignore this, trust the rev
6052 var oldRev = doc._rev;
6053 var id = doc._id;
6054
6055 var txn = opts.ctx || new LevelTransaction();
6056
6057 txn.get(stores.localStore, id, function (err, resp) {
6058 if (err && oldRev) {
6059 return callback(createError(REV_CONFLICT));
6060 }
6061 if (resp && resp._rev !== oldRev) {
6062 return callback(createError(REV_CONFLICT));
6063 }
6064 doc._rev =
6065 oldRev ? '0-' + (parseInt(oldRev.split('-')[1], 10) + 1) : '0-1';
6066 var batch = [
6067 {
6068 type: 'put',
6069 prefix: stores.localStore,
6070 key: id,
6071 value: doc
6072 }
6073 ];
6074
6075 txn.batch(batch);
6076 var ret = {ok: true, id: doc._id, rev: doc._rev};
6077
6078 if (opts.ctx) {
6079 // don't execute immediately
6080 return callback(null, ret);
6081 }
6082 txn.execute(db, function (err) {
6083 /* istanbul ignore if */
6084 if (err) {
6085 return callback(err);
6086 }
6087 callback(null, ret);
6088 });
6089 });
6090 };
6091
6092 api._removeLocal = function (doc, opts, callback) {
6093 if (typeof opts === 'function') {
6094 callback = opts;
6095 opts = {};
6096 }
6097 if (opts.ctx) {
6098 api._removeLocalNoLock(doc, opts, callback);
6099 } else {
6100 api._removeLocalWithLock(doc, opts, callback);
6101 }
6102 };
6103
6104 api._removeLocalWithLock = writeLock(function (doc, opts, callback) {
6105 api._removeLocalNoLock(doc, opts, callback);
6106 });
6107
6108 // the NoLock version is for use by bulkDocs
6109 api._removeLocalNoLock = function (doc, opts, callback) {
6110 var txn = opts.ctx || new LevelTransaction();
6111 txn.get(stores.localStore, doc._id, function (err, resp) {
6112 if (err) {
6113 /* istanbul ignore if */
6114 if (err.name !== 'NotFoundError') {
6115 return callback(err);
6116 } else {
6117 return callback(createError(MISSING_DOC));
6118 }
6119 }
6120 if (resp._rev !== doc._rev) {
6121 return callback(createError(REV_CONFLICT));
6122 }
6123 txn.batch([{
6124 prefix: stores.localStore,
6125 type: 'del',
6126 key: doc._id
6127 }]);
6128 var ret = {ok: true, id: doc._id, rev: '0-0'};
6129 if (opts.ctx) {
6130 // don't execute immediately
6131 return callback(null, ret);
6132 }
6133 txn.execute(db, function (err) {
6134 /* istanbul ignore if */
6135 if (err) {
6136 return callback(err);
6137 }
6138 callback(null, ret);
6139 });
6140 });
6141 };
6142
6143 // close and delete open leveldb stores
6144 api._destroy = function (opts, callback) {
6145 var dbStore;
6146 var leveldownName = functionName(leveldown);
6147 /* istanbul ignore else */
6148 if (dbStores.has(leveldownName)) {
6149 dbStore = dbStores.get(leveldownName);
6150 } else {
6151 return callDestroy(name, callback);
6152 }
6153
6154 /* istanbul ignore else */
6155 if (dbStore.has(name)) {
6156 levelChanges.removeAllListeners(name);
6157
6158 dbStore.get(name).close(function () {
6159 dbStore.delete(name);
6160 callDestroy(name, callback);
6161 });
6162 } else {
6163 callDestroy(name, callback);
6164 }
6165 };
6166 function callDestroy(name, cb) {
6167 // May not exist if leveldown is backed by memory adapter
6168 if ('destroy' in leveldown) {
6169 leveldown.destroy(name, cb);
6170 }
6171 }
6172}
6173
6174// require leveldown. provide verbose output on error as it is the default
6175// nodejs adapter, which we do not provide for the user
6176/* istanbul ignore next */
6177var requireLeveldown = function () {
6178 try {
6179 return require('leveldown');
6180 } catch (err) {
6181 /* eslint no-ex-assign: 0*/
6182 err = err || 'leveldown import error';
6183 if (err.code === 'MODULE_NOT_FOUND') {
6184 // handle leveldown not installed case
6185 return new Error([
6186 'the \'leveldown\' package is not available. install it, or,',
6187 'specify another storage backend using the \'db\' option'
6188 ].join(' '));
6189 } else if (err.message && err.message.match('Module version mismatch')) {
6190 // handle common user enviornment error
6191 return new Error([
6192 err.message,
6193 'This generally implies that leveldown was built with a different',
6194 'version of node than that which is running now. You may try',
6195 'fully removing and reinstalling PouchDB or leveldown to resolve.'
6196 ].join(' '));
6197 }
6198 // handle general internal nodejs require error
6199 return new Error(err.toString() + ': unable to import leveldown');
6200 }
6201};
6202
6203var stores = [
6204 'document-store',
6205 'by-sequence',
6206 'attach-store',
6207 'attach-binary-store'
6208];
6209function formatSeq(n) {
6210 return ('0000000000000000' + n).slice(-16);
6211}
6212var UPDATE_SEQ_KEY$1 = '_local_last_update_seq';
6213var DOC_COUNT_KEY$1 = '_local_doc_count';
6214var UUID_KEY$1 = '_local_uuid';
6215
6216var doMigrationOne = function (name, db, callback) {
6217 // local require to prevent crashing if leveldown isn't installed.
6218 var leveldown = require("leveldown");
6219
6220 var base = path.resolve(name);
6221 function move(store, index, cb) {
6222 var storePath = path.join(base, store);
6223 var opts;
6224 if (index === 3) {
6225 opts = {
6226 valueEncoding: 'binary'
6227 };
6228 } else {
6229 opts = {
6230 valueEncoding: 'json'
6231 };
6232 }
6233 var sub = db.sublevel(store, opts);
6234 var orig = level(storePath, opts);
6235 var from = orig.createReadStream();
6236 var writeStream = new LevelWriteStream(sub);
6237 var to = writeStream();
6238 from.on('end', function () {
6239 orig.close(function (err) {
6240 cb(err, storePath);
6241 });
6242 });
6243 from.pipe(to);
6244 }
6245 fs.unlink(base + '.uuid', function (err) {
6246 if (err) {
6247 return callback();
6248 }
6249 var todo = 4;
6250 var done = [];
6251 stores.forEach(function (store, i) {
6252 move(store, i, function (err, storePath) {
6253 /* istanbul ignore if */
6254 if (err) {
6255 return callback(err);
6256 }
6257 done.push(storePath);
6258 if (!(--todo)) {
6259 done.forEach(function (item) {
6260 leveldown.destroy(item, function () {
6261 if (++todo === done.length) {
6262 fs.rmdir(base, callback);
6263 }
6264 });
6265 });
6266 }
6267 });
6268 });
6269 });
6270};
6271var doMigrationTwo = function (db, stores, callback) {
6272 var batches = [];
6273 stores.bySeqStore.get(UUID_KEY$1, function (err, value) {
6274 if (err) {
6275 // no uuid key, so don't need to migrate;
6276 return callback();
6277 }
6278 batches.push({
6279 key: UUID_KEY$1,
6280 value: value,
6281 prefix: stores.metaStore,
6282 type: 'put',
6283 valueEncoding: 'json'
6284 });
6285 batches.push({
6286 key: UUID_KEY$1,
6287 prefix: stores.bySeqStore,
6288 type: 'del'
6289 });
6290 stores.bySeqStore.get(DOC_COUNT_KEY$1, function (err, value) {
6291 if (value) {
6292 // if no doc count key,
6293 // just skip
6294 // we can live with this
6295 batches.push({
6296 key: DOC_COUNT_KEY$1,
6297 value: value,
6298 prefix: stores.metaStore,
6299 type: 'put',
6300 valueEncoding: 'json'
6301 });
6302 batches.push({
6303 key: DOC_COUNT_KEY$1,
6304 prefix: stores.bySeqStore,
6305 type: 'del'
6306 });
6307 }
6308 stores.bySeqStore.get(UPDATE_SEQ_KEY$1, function (err, value) {
6309 if (value) {
6310 // if no UPDATE_SEQ_KEY
6311 // just skip
6312 // we've gone to far to stop.
6313 batches.push({
6314 key: UPDATE_SEQ_KEY$1,
6315 value: value,
6316 prefix: stores.metaStore,
6317 type: 'put',
6318 valueEncoding: 'json'
6319 });
6320 batches.push({
6321 key: UPDATE_SEQ_KEY$1,
6322 prefix: stores.bySeqStore,
6323 type: 'del'
6324 });
6325 }
6326 var deletedSeqs = {};
6327 stores.docStore.createReadStream({
6328 startKey: '_',
6329 endKey: '_\xFF'
6330 }).pipe(obj(function (ch, _, next) {
6331 if (!isLocalId(ch.key)) {
6332 return next();
6333 }
6334 batches.push({
6335 key: ch.key,
6336 prefix: stores.docStore,
6337 type: 'del'
6338 });
6339 var winner = winningRev(ch.value);
6340 Object.keys(ch.value.rev_map).forEach(function (key) {
6341 if (key !== 'winner') {
6342 this.push(formatSeq(ch.value.rev_map[key]));
6343 }
6344 }, this);
6345 var winningSeq = ch.value.rev_map[winner];
6346 stores.bySeqStore.get(formatSeq(winningSeq), function (err, value) {
6347 if (!err) {
6348 batches.push({
6349 key: ch.key,
6350 value: value,
6351 prefix: stores.localStore,
6352 type: 'put',
6353 valueEncoding: 'json'
6354 });
6355 }
6356 next();
6357 });
6358
6359 })).pipe(obj(function (seq, _, next) {
6360 /* istanbul ignore if */
6361 if (deletedSeqs[seq]) {
6362 return next();
6363 }
6364 deletedSeqs[seq] = true;
6365 stores.bySeqStore.get(seq, function (err, resp) {
6366 /* istanbul ignore if */
6367 if (err || !isLocalId(resp._id)) {
6368 return next();
6369 }
6370 batches.push({
6371 key: seq,
6372 prefix: stores.bySeqStore,
6373 type: 'del'
6374 });
6375 next();
6376 });
6377 }, function () {
6378 db.batch(batches, callback);
6379 }));
6380 });
6381 });
6382 });
6383
6384};
6385
6386var migrate = {
6387 doMigrationOne: doMigrationOne,
6388 doMigrationTwo: doMigrationTwo
6389};
6390
6391function LevelDownPouch(opts, callback) {
6392
6393 // Users can pass in their own leveldown alternative here, in which case
6394 // it overrides the default one. (This is in addition to the custom builds.)
6395 var leveldown = opts.db;
6396
6397 /* istanbul ignore else */
6398 if (!leveldown) {
6399 leveldown = requireLeveldown();
6400
6401 /* istanbul ignore if */
6402 if (leveldown instanceof Error) {
6403 return callback(leveldown);
6404 }
6405 }
6406
6407 var _opts = $inject_Object_assign({
6408 db: leveldown,
6409 migrate: migrate
6410 }, opts);
6411
6412 LevelPouch.call(this, _opts, callback);
6413}
6414
6415// overrides for normal LevelDB behavior on Node
6416LevelDownPouch.valid = function () {
6417 return true;
6418};
6419LevelDownPouch.use_prefix = false;
6420
6421function LevelPouch$1 (PouchDB) {
6422 PouchDB.adapter('leveldb', LevelDownPouch, true);
6423}
6424
6425// dead simple promise pool, inspired by https://github.com/timdp/es6-promise-pool
6426// but much smaller in code size. limits the number of concurrent promises that are executed
6427
6428
6429function pool(promiseFactories, limit) {
6430 return new Promise(function (resolve, reject) {
6431 var running = 0;
6432 var current = 0;
6433 var done = 0;
6434 var len = promiseFactories.length;
6435 var err;
6436
6437 function runNext() {
6438 running++;
6439 promiseFactories[current++]().then(onSuccess, onError);
6440 }
6441
6442 function doNext() {
6443 if (++done === len) {
6444 /* istanbul ignore if */
6445 if (err) {
6446 reject(err);
6447 } else {
6448 resolve();
6449 }
6450 } else {
6451 runNextBatch();
6452 }
6453 }
6454
6455 function onSuccess() {
6456 running--;
6457 doNext();
6458 }
6459
6460 /* istanbul ignore next */
6461 function onError(thisErr) {
6462 running--;
6463 err = err || thisErr;
6464 doNext();
6465 }
6466
6467 function runNextBatch() {
6468 while (running < limit && current < len) {
6469 runNext();
6470 }
6471 }
6472
6473 runNextBatch();
6474 });
6475}
6476
6477var CHANGES_BATCH_SIZE = 25;
6478var MAX_SIMULTANEOUS_REVS = 50;
6479var CHANGES_TIMEOUT_BUFFER = 5000;
6480var DEFAULT_HEARTBEAT = 10000;
6481
6482var supportsBulkGetMap = {};
6483
6484function readAttachmentsAsBlobOrBuffer(row) {
6485 var doc = row.doc || row.ok;
6486 var atts = doc._attachments;
6487 if (!atts) {
6488 return;
6489 }
6490 Object.keys(atts).forEach(function (filename) {
6491 var att = atts[filename];
6492 att.data = b64ToBluffer(att.data, att.content_type);
6493 });
6494}
6495
6496function encodeDocId(id) {
6497 if (/^_design/.test(id)) {
6498 return '_design/' + encodeURIComponent(id.slice(8));
6499 }
6500 if (/^_local/.test(id)) {
6501 return '_local/' + encodeURIComponent(id.slice(7));
6502 }
6503 return encodeURIComponent(id);
6504}
6505
6506function preprocessAttachments$1(doc) {
6507 if (!doc._attachments || !Object.keys(doc._attachments)) {
6508 return Promise.resolve();
6509 }
6510
6511 return Promise.all(Object.keys(doc._attachments).map(function (key) {
6512 var attachment = doc._attachments[key];
6513 if (attachment.data && typeof attachment.data !== 'string') {
6514 return new Promise(function (resolve) {
6515 blobToBase64(attachment.data, resolve);
6516 }).then(function (b64) {
6517 attachment.data = b64;
6518 });
6519 }
6520 }));
6521}
6522
6523function hasUrlPrefix(opts) {
6524 if (!opts.prefix) {
6525 return false;
6526 }
6527 var protocol = parseUri(opts.prefix).protocol;
6528 return protocol === 'http' || protocol === 'https';
6529}
6530
6531// Get all the information you possibly can about the URI given by name and
6532// return it as a suitable object.
6533function getHost(name, opts) {
6534 // encode db name if opts.prefix is a url (#5574)
6535 if (hasUrlPrefix(opts)) {
6536 var dbName = opts.name.substr(opts.prefix.length);
6537 // Ensure prefix has a trailing slash
6538 var prefix = opts.prefix.replace(/\/?$/, '/');
6539 name = prefix + encodeURIComponent(dbName);
6540 }
6541
6542 var uri = parseUri(name);
6543 if (uri.user || uri.password) {
6544 uri.auth = {username: uri.user, password: uri.password};
6545 }
6546
6547 // Split the path part of the URI into parts using '/' as the delimiter
6548 // after removing any leading '/' and any trailing '/'
6549 var parts = uri.path.replace(/(^\/|\/$)/g, '').split('/');
6550
6551 uri.db = parts.pop();
6552 // Prevent double encoding of URI component
6553 if (uri.db.indexOf('%') === -1) {
6554 uri.db = encodeURIComponent(uri.db);
6555 }
6556
6557 uri.path = parts.join('/');
6558
6559 return uri;
6560}
6561
6562// Generate a URL with the host data given by opts and the given path
6563function genDBUrl(opts, path$$1) {
6564 return genUrl(opts, opts.db + '/' + path$$1);
6565}
6566
6567// Generate a URL with the host data given by opts and the given path
6568function genUrl(opts, path$$1) {
6569 // If the host already has a path, then we need to have a path delimiter
6570 // Otherwise, the path delimiter is the empty string
6571 var pathDel = !opts.path ? '' : '/';
6572
6573 // If the host already has a path, then we need to have a path delimiter
6574 // Otherwise, the path delimiter is the empty string
6575 return opts.protocol + '://' + opts.host +
6576 (opts.port ? (':' + opts.port) : '') +
6577 '/' + opts.path + pathDel + path$$1;
6578}
6579
6580function paramsToStr(params) {
6581 return '?' + Object.keys(params).map(function (k) {
6582 return k + '=' + encodeURIComponent(params[k]);
6583 }).join('&');
6584}
6585
6586function shouldCacheBust(opts) {
6587 var ua = (typeof navigator !== 'undefined' && navigator.userAgent) ?
6588 navigator.userAgent.toLowerCase() : '';
6589 var isIE = ua.indexOf('msie') !== -1;
6590 var isTrident = ua.indexOf('trident') !== -1;
6591 var isEdge = ua.indexOf('edge') !== -1;
6592 var isGET = !('method' in opts) || opts.method === 'GET';
6593 return (isIE || isTrident || isEdge) && isGET;
6594}
6595
6596// Implements the PouchDB API for dealing with CouchDB instances over HTTP
6597function HttpPouch(opts, callback) {
6598
6599 // The functions that will be publicly available for HttpPouch
6600 var api = this;
6601
6602 var host = getHost(opts.name, opts);
6603 var dbUrl = genDBUrl(host, '');
6604
6605 opts = clone(opts);
6606
6607 var ourFetch = function (url, options) {
6608
6609 options = options || {};
6610 options.headers = options.headers || new Headers();
6611
6612 if (opts.auth || host.auth) {
6613 var nAuth = opts.auth || host.auth;
6614 var str = nAuth.username + ':' + nAuth.password;
6615 var token = thisBtoa(unescape(encodeURIComponent(str)));
6616 options.headers.set('Authorization', 'Basic ' + token);
6617 }
6618
6619 var headers = opts.headers || {};
6620 Object.keys(headers).forEach(function (key) {
6621 options.headers.append(key, headers[key]);
6622 });
6623
6624 /* istanbul ignore if */
6625 if (shouldCacheBust(options)) {
6626 url += (url.indexOf('?') === -1 ? '?' : '&') + '_nonce=' + Date.now();
6627 }
6628
6629 var fetchFun = opts.fetch || fetch;
6630 return fetchFun(url, options);
6631 };
6632
6633 function adapterFun$$1(name, fun) {
6634 return adapterFun(name, getArguments(function (args) {
6635 setup().then(function () {
6636 return fun.apply(this, args);
6637 }).catch(function (e) {
6638 var callback = args.pop();
6639 callback(e);
6640 });
6641 })).bind(api);
6642 }
6643
6644 function fetchJSON(url, options, callback) {
6645
6646 var result = {};
6647
6648 options = options || {};
6649 options.headers = options.headers || new Headers();
6650
6651 if (!options.headers.get('Content-Type')) {
6652 options.headers.set('Content-Type', 'application/json');
6653 }
6654 if (!options.headers.get('Accept')) {
6655 options.headers.set('Accept', 'application/json');
6656 }
6657
6658 return ourFetch(url, options).then(function (response) {
6659 result.ok = response.ok;
6660 result.status = response.status;
6661 return response.json();
6662 }).then(function (json) {
6663 result.data = json;
6664 if (!result.ok) {
6665 result.data.status = result.status;
6666 var err = generateErrorFromResponse(result.data);
6667 if (callback) {
6668 return callback(err);
6669 } else {
6670 throw err;
6671 }
6672 }
6673
6674 if (Array.isArray(result.data)) {
6675 result.data = result.data.map(function (v) {
6676 if (v.error || v.missing) {
6677 return generateErrorFromResponse(v);
6678 } else {
6679 return v;
6680 }
6681 });
6682 }
6683
6684 if (callback) {
6685 callback(null, result.data);
6686 } else {
6687 return result;
6688 }
6689 });
6690 }
6691
6692 var setupPromise;
6693
6694 function setup() {
6695 if (opts.skip_setup) {
6696 return Promise.resolve();
6697 }
6698
6699 // If there is a setup in process or previous successful setup
6700 // done then we will use that
6701 // If previous setups have been rejected we will try again
6702 if (setupPromise) {
6703 return setupPromise;
6704 }
6705
6706 setupPromise = fetchJSON(dbUrl).catch(function (err) {
6707 if (err && err.status && err.status === 404) {
6708 return fetchJSON(dbUrl, {method: 'PUT'});
6709 } else {
6710 return Promise.reject(err);
6711 }
6712 }).catch(function (err) {
6713 // If we try to create a database that already exists, skipped in
6714 // istanbul since its catching a race condition.
6715 /* istanbul ignore if */
6716 if (err && err.status && err.status === 412) {
6717 return true;
6718 }
6719 return Promise.reject(err);
6720 });
6721
6722 setupPromise.catch(function () {
6723 setupPromise = null;
6724 });
6725
6726 return setupPromise;
6727 }
6728
6729 nextTick(function () {
6730 callback(null, api);
6731 });
6732
6733 api._remote = true;
6734
6735 /* istanbul ignore next */
6736 api.type = function () {
6737 return 'http';
6738 };
6739
6740 api.id = adapterFun$$1('id', function (callback) {
6741 ourFetch(genUrl(host, '')).then(function (response) {
6742 return response.json();
6743 }).then(function (result) {
6744 var uuid$$1 = (result && result.uuid) ?
6745 (result.uuid + host.db) : genDBUrl(host, '');
6746 callback(null, uuid$$1);
6747 }).catch(function (err) {
6748 callback(err);
6749 });
6750 });
6751
6752 // Sends a POST request to the host calling the couchdb _compact function
6753 // version: The version of CouchDB it is running
6754 api.compact = adapterFun$$1('compact', function (opts, callback) {
6755 if (typeof opts === 'function') {
6756 callback = opts;
6757 opts = {};
6758 }
6759 opts = clone(opts);
6760
6761 fetchJSON(genDBUrl(host, '_compact'), {method: 'POST'}).then(function () {
6762 function ping() {
6763 api.info(function (err, res$$1) {
6764 // CouchDB may send a "compact_running:true" if it's
6765 // already compacting. PouchDB Server doesn't.
6766 /* istanbul ignore else */
6767 if (res$$1 && !res$$1.compact_running) {
6768 callback(null, {ok: true});
6769 } else {
6770 setTimeout(ping, opts.interval || 200);
6771 }
6772 });
6773 }
6774 // Ping the http if it's finished compaction
6775 ping();
6776 });
6777 });
6778
6779 api.bulkGet = adapterFun('bulkGet', function (opts, callback) {
6780 var self = this;
6781
6782 function doBulkGet(cb) {
6783 var params = {};
6784 if (opts.revs) {
6785 params.revs = true;
6786 }
6787 if (opts.attachments) {
6788 /* istanbul ignore next */
6789 params.attachments = true;
6790 }
6791 if (opts.latest) {
6792 params.latest = true;
6793 }
6794 fetchJSON(genDBUrl(host, '_bulk_get' + paramsToStr(params)), {
6795 method: 'POST',
6796 body: JSON.stringify({ docs: opts.docs})
6797 }).then(function (result) {
6798 if (opts.attachments && opts.binary) {
6799 result.data.results.forEach(function (res$$1) {
6800 res$$1.docs.forEach(readAttachmentsAsBlobOrBuffer);
6801 });
6802 }
6803 cb(null, result.data);
6804 }).catch(cb);
6805 }
6806
6807 /* istanbul ignore next */
6808 function doBulkGetShim() {
6809 // avoid "url too long error" by splitting up into multiple requests
6810 var batchSize = MAX_SIMULTANEOUS_REVS;
6811 var numBatches = Math.ceil(opts.docs.length / batchSize);
6812 var numDone = 0;
6813 var results = new Array(numBatches);
6814
6815 function onResult(batchNum) {
6816 return function (err, res$$1) {
6817 // err is impossible because shim returns a list of errs in that case
6818 results[batchNum] = res$$1.results;
6819 if (++numDone === numBatches) {
6820 callback(null, {results: flatten(results)});
6821 }
6822 };
6823 }
6824
6825 for (var i = 0; i < numBatches; i++) {
6826 var subOpts = pick(opts, ['revs', 'attachments', 'binary', 'latest']);
6827 subOpts.docs = opts.docs.slice(i * batchSize,
6828 Math.min(opts.docs.length, (i + 1) * batchSize));
6829 bulkGet(self, subOpts, onResult(i));
6830 }
6831 }
6832
6833 // mark the whole database as either supporting or not supporting _bulk_get
6834 var dbUrl = genUrl(host, '');
6835 var supportsBulkGet = supportsBulkGetMap[dbUrl];
6836
6837 /* istanbul ignore next */
6838 if (typeof supportsBulkGet !== 'boolean') {
6839 // check if this database supports _bulk_get
6840 doBulkGet(function (err, res$$1) {
6841 if (err) {
6842 supportsBulkGetMap[dbUrl] = false;
6843 res(
6844 err.status,
6845 'PouchDB is just detecting if the remote ' +
6846 'supports the _bulk_get API.'
6847 );
6848 doBulkGetShim();
6849 } else {
6850 supportsBulkGetMap[dbUrl] = true;
6851 callback(null, res$$1);
6852 }
6853 });
6854 } else if (supportsBulkGet) {
6855 doBulkGet(callback);
6856 } else {
6857 doBulkGetShim();
6858 }
6859 });
6860
6861 // Calls GET on the host, which gets back a JSON string containing
6862 // couchdb: A welcome string
6863 // version: The version of CouchDB it is running
6864 api._info = function (callback) {
6865 setup().then(function () {
6866 return ourFetch(genDBUrl(host, ''));
6867 }).then(function (response) {
6868 return response.json();
6869 }).then(function (info) {
6870 info.host = genDBUrl(host, '');
6871 callback(null, info);
6872 }).catch(callback);
6873 };
6874
6875 api.fetch = function (path$$1, options) {
6876 return setup().then(function () {
6877 return ourFetch(genDBUrl(host, path$$1), options);
6878 });
6879 };
6880
6881 // Get the document with the given id from the database given by host.
6882 // The id could be solely the _id in the database, or it may be a
6883 // _design/ID or _local/ID path
6884 api.get = adapterFun$$1('get', function (id, opts, callback) {
6885 // If no options were given, set the callback to the second parameter
6886 if (typeof opts === 'function') {
6887 callback = opts;
6888 opts = {};
6889 }
6890 opts = clone(opts);
6891
6892 // List of parameters to add to the GET request
6893 var params = {};
6894
6895 if (opts.revs) {
6896 params.revs = true;
6897 }
6898
6899 if (opts.revs_info) {
6900 params.revs_info = true;
6901 }
6902
6903 if (opts.latest) {
6904 params.latest = true;
6905 }
6906
6907 if (opts.open_revs) {
6908 if (opts.open_revs !== "all") {
6909 opts.open_revs = JSON.stringify(opts.open_revs);
6910 }
6911 params.open_revs = opts.open_revs;
6912 }
6913
6914 if (opts.rev) {
6915 params.rev = opts.rev;
6916 }
6917
6918 if (opts.conflicts) {
6919 params.conflicts = opts.conflicts;
6920 }
6921
6922 /* istanbul ignore if */
6923 if (opts.update_seq) {
6924 params.update_seq = opts.update_seq;
6925 }
6926
6927 id = encodeDocId(id);
6928
6929 function fetchAttachments(doc) {
6930 var atts = doc._attachments;
6931 var filenames = atts && Object.keys(atts);
6932 if (!atts || !filenames.length) {
6933 return;
6934 }
6935 // we fetch these manually in separate XHRs, because
6936 // Sync Gateway would normally send it back as multipart/mixed,
6937 // which we cannot parse. Also, this is more efficient than
6938 // receiving attachments as base64-encoded strings.
6939 function fetchData(filename) {
6940 var att = atts[filename];
6941 var path$$1 = encodeDocId(doc._id) + '/' + encodeAttachmentId(filename) +
6942 '?rev=' + doc._rev;
6943 return ourFetch(genDBUrl(host, path$$1)).then(function (response) {
6944 if (typeof process !== 'undefined' && !process.browser) {
6945 return response.buffer();
6946 } else {
6947 /* istanbul ignore next */
6948 return response.blob();
6949 }
6950 }).then(function (blob) {
6951 if (opts.binary) {
6952 // TODO: Can we remove this?
6953 if (typeof process !== 'undefined' && !process.browser) {
6954 blob.type = att.content_type;
6955 }
6956 return blob;
6957 }
6958 return new Promise(function (resolve) {
6959 blobToBase64(blob, resolve);
6960 });
6961 }).then(function (data) {
6962 delete att.stub;
6963 delete att.length;
6964 att.data = data;
6965 });
6966 }
6967
6968 var promiseFactories = filenames.map(function (filename) {
6969 return function () {
6970 return fetchData(filename);
6971 };
6972 });
6973
6974 // This limits the number of parallel xhr requests to 5 any time
6975 // to avoid issues with maximum browser request limits
6976 return pool(promiseFactories, 5);
6977 }
6978
6979 function fetchAllAttachments(docOrDocs) {
6980 if (Array.isArray(docOrDocs)) {
6981 return Promise.all(docOrDocs.map(function (doc) {
6982 if (doc.ok) {
6983 return fetchAttachments(doc.ok);
6984 }
6985 }));
6986 }
6987 return fetchAttachments(docOrDocs);
6988 }
6989
6990 var url = genDBUrl(host, id + paramsToStr(params));
6991 fetchJSON(url).then(function (res$$1) {
6992 return Promise.resolve().then(function () {
6993 if (opts.attachments) {
6994 return fetchAllAttachments(res$$1.data);
6995 }
6996 }).then(function () {
6997 callback(null, res$$1.data);
6998 });
6999 }).catch(function (e) {
7000 e.docId = id;
7001 callback(e);
7002 });
7003 });
7004
7005
7006 // Delete the document given by doc from the database given by host.
7007 api.remove = adapterFun$$1('remove', function (docOrId, optsOrRev, opts, cb) {
7008 var doc;
7009 if (typeof optsOrRev === 'string') {
7010 // id, rev, opts, callback style
7011 doc = {
7012 _id: docOrId,
7013 _rev: optsOrRev
7014 };
7015 if (typeof opts === 'function') {
7016 cb = opts;
7017 opts = {};
7018 }
7019 } else {
7020 // doc, opts, callback style
7021 doc = docOrId;
7022 if (typeof optsOrRev === 'function') {
7023 cb = optsOrRev;
7024 opts = {};
7025 } else {
7026 cb = opts;
7027 opts = optsOrRev;
7028 }
7029 }
7030
7031 var rev = (doc._rev || opts.rev);
7032 var url = genDBUrl(host, encodeDocId(doc._id)) + '?rev=' + rev;
7033
7034 fetchJSON(url, {method: 'DELETE'}, cb).catch(cb);
7035 });
7036
7037 function encodeAttachmentId(attachmentId) {
7038 return attachmentId.split("/").map(encodeURIComponent).join("/");
7039 }
7040
7041 // Get the attachment
7042 api.getAttachment = adapterFun$$1('getAttachment', function (docId, attachmentId,
7043 opts, callback) {
7044 if (typeof opts === 'function') {
7045 callback = opts;
7046 opts = {};
7047 }
7048 var params = opts.rev ? ('?rev=' + opts.rev) : '';
7049 var url = genDBUrl(host, encodeDocId(docId)) + '/' +
7050 encodeAttachmentId(attachmentId) + params;
7051 var contentType;
7052 ourFetch(url, {method: 'GET'}).then(function (response) {
7053 contentType = response.headers.get('content-type');
7054 if (!response.ok) {
7055 throw response;
7056 } else {
7057 if (typeof process !== 'undefined' && !process.browser) {
7058 return response.buffer();
7059 } else {
7060 /* istanbul ignore next */
7061 return response.blob();
7062 }
7063 }
7064 }).then(function (blob) {
7065 // TODO: also remove
7066 if (typeof process !== 'undefined' && !process.browser) {
7067 blob.type = contentType;
7068 }
7069 callback(null, blob);
7070 }).catch(function (err) {
7071 callback(err);
7072 });
7073 });
7074
7075 // Remove the attachment given by the id and rev
7076 api.removeAttachment = adapterFun$$1('removeAttachment', function (docId,
7077 attachmentId,
7078 rev,
7079 callback) {
7080 var url = genDBUrl(host, encodeDocId(docId) + '/' +
7081 encodeAttachmentId(attachmentId)) + '?rev=' + rev;
7082 fetchJSON(url, {method: 'DELETE'}, callback).catch(callback);
7083 });
7084
7085 // Add the attachment given by blob and its contentType property
7086 // to the document with the given id, the revision given by rev, and
7087 // add it to the database given by host.
7088 api.putAttachment = adapterFun$$1('putAttachment', function (docId, attachmentId,
7089 rev, blob,
7090 type, callback) {
7091 if (typeof type === 'function') {
7092 callback = type;
7093 type = blob;
7094 blob = rev;
7095 rev = null;
7096 }
7097 var id = encodeDocId(docId) + '/' + encodeAttachmentId(attachmentId);
7098 var url = genDBUrl(host, id);
7099 if (rev) {
7100 url += '?rev=' + rev;
7101 }
7102
7103 if (typeof blob === 'string') {
7104 // input is assumed to be a base64 string
7105 var binary;
7106 try {
7107 binary = thisAtob(blob);
7108 } catch (err) {
7109 return callback(createError(BAD_ARG,
7110 'Attachment is not a valid base64 string'));
7111 }
7112 blob = binary ? binStringToBluffer(binary, type) : '';
7113 }
7114
7115 // Add the attachment
7116 fetchJSON(url, {
7117 headers: new Headers({'Content-Type': type}),
7118 method: 'PUT',
7119 body: blob
7120 }, callback).catch(callback);
7121 });
7122
7123 // Update/create multiple documents given by req in the database
7124 // given by host.
7125 api._bulkDocs = function (req, opts, callback) {
7126 // If new_edits=false then it prevents the database from creating
7127 // new revision numbers for the documents. Instead it just uses
7128 // the old ones. This is used in database replication.
7129 req.new_edits = opts.new_edits;
7130
7131 setup().then(function () {
7132 return Promise.all(req.docs.map(preprocessAttachments$1));
7133 }).then(function () {
7134 // Update/create the documents
7135 return fetchJSON(genDBUrl(host, '_bulk_docs'), {
7136 method: 'POST',
7137 body: JSON.stringify(req)
7138 }, callback);
7139 }).catch(callback);
7140 };
7141
7142
7143 // Update/create document
7144 api._put = function (doc, opts, callback) {
7145 setup().then(function () {
7146 return preprocessAttachments$1(doc);
7147 }).then(function () {
7148 return fetchJSON(genDBUrl(host, encodeDocId(doc._id)), {
7149 method: 'PUT',
7150 body: JSON.stringify(doc)
7151 });
7152 }).then(function (result) {
7153 callback(null, result.data);
7154 }).catch(function (err) {
7155 err.docId = doc && doc._id;
7156 callback(err);
7157 });
7158 };
7159
7160
7161 // Get a listing of the documents in the database given
7162 // by host and ordered by increasing id.
7163 api.allDocs = adapterFun$$1('allDocs', function (opts, callback) {
7164 if (typeof opts === 'function') {
7165 callback = opts;
7166 opts = {};
7167 }
7168 opts = clone(opts);
7169
7170 // List of parameters to add to the GET request
7171 var params = {};
7172 var body;
7173 var method = 'GET';
7174
7175 if (opts.conflicts) {
7176 params.conflicts = true;
7177 }
7178
7179 /* istanbul ignore if */
7180 if (opts.update_seq) {
7181 params.update_seq = true;
7182 }
7183
7184 if (opts.descending) {
7185 params.descending = true;
7186 }
7187
7188 if (opts.include_docs) {
7189 params.include_docs = true;
7190 }
7191
7192 // added in CouchDB 1.6.0
7193 if (opts.attachments) {
7194 params.attachments = true;
7195 }
7196
7197 if (opts.key) {
7198 params.key = JSON.stringify(opts.key);
7199 }
7200
7201 if (opts.start_key) {
7202 opts.startkey = opts.start_key;
7203 }
7204
7205 if (opts.startkey) {
7206 params.startkey = JSON.stringify(opts.startkey);
7207 }
7208
7209 if (opts.end_key) {
7210 opts.endkey = opts.end_key;
7211 }
7212
7213 if (opts.endkey) {
7214 params.endkey = JSON.stringify(opts.endkey);
7215 }
7216
7217 if (typeof opts.inclusive_end !== 'undefined') {
7218 params.inclusive_end = !!opts.inclusive_end;
7219 }
7220
7221 if (typeof opts.limit !== 'undefined') {
7222 params.limit = opts.limit;
7223 }
7224
7225 if (typeof opts.skip !== 'undefined') {
7226 params.skip = opts.skip;
7227 }
7228
7229 var paramStr = paramsToStr(params);
7230
7231 if (typeof opts.keys !== 'undefined') {
7232 method = 'POST';
7233 body = {keys: opts.keys};
7234 }
7235
7236 fetchJSON(genDBUrl(host, '_all_docs' + paramStr), {
7237 method: method,
7238 body: JSON.stringify(body)
7239 }).then(function (result) {
7240 if (opts.include_docs && opts.attachments && opts.binary) {
7241 result.data.rows.forEach(readAttachmentsAsBlobOrBuffer);
7242 }
7243 callback(null, result.data);
7244 }).catch(callback);
7245 });
7246
7247 // Get a list of changes made to documents in the database given by host.
7248 // TODO According to the README, there should be two other methods here,
7249 // api.changes.addListener and api.changes.removeListener.
7250 api._changes = function (opts) {
7251
7252 // We internally page the results of a changes request, this means
7253 // if there is a large set of changes to be returned we can start
7254 // processing them quicker instead of waiting on the entire
7255 // set of changes to return and attempting to process them at once
7256 var batchSize = 'batch_size' in opts ? opts.batch_size : CHANGES_BATCH_SIZE;
7257
7258 opts = clone(opts);
7259
7260 if (opts.continuous && !('heartbeat' in opts)) {
7261 opts.heartbeat = DEFAULT_HEARTBEAT;
7262 }
7263
7264 var requestTimeout = ('timeout' in opts) ? opts.timeout : 30 * 1000;
7265
7266 // ensure CHANGES_TIMEOUT_BUFFER applies
7267 if ('timeout' in opts && opts.timeout &&
7268 (requestTimeout - opts.timeout) < CHANGES_TIMEOUT_BUFFER) {
7269 requestTimeout = opts.timeout + CHANGES_TIMEOUT_BUFFER;
7270 }
7271
7272 /* istanbul ignore if */
7273 if ('heartbeat' in opts && opts.heartbeat &&
7274 (requestTimeout - opts.heartbeat) < CHANGES_TIMEOUT_BUFFER) {
7275 requestTimeout = opts.heartbeat + CHANGES_TIMEOUT_BUFFER;
7276 }
7277
7278 var params = {};
7279 if ('timeout' in opts && opts.timeout) {
7280 params.timeout = opts.timeout;
7281 }
7282
7283 var limit = (typeof opts.limit !== 'undefined') ? opts.limit : false;
7284 var leftToFetch = limit;
7285
7286 if (opts.style) {
7287 params.style = opts.style;
7288 }
7289
7290 if (opts.include_docs || opts.filter && typeof opts.filter === 'function') {
7291 params.include_docs = true;
7292 }
7293
7294 if (opts.attachments) {
7295 params.attachments = true;
7296 }
7297
7298 if (opts.continuous) {
7299 params.feed = 'longpoll';
7300 }
7301
7302 if (opts.seq_interval) {
7303 params.seq_interval = opts.seq_interval;
7304 }
7305
7306 if (opts.conflicts) {
7307 params.conflicts = true;
7308 }
7309
7310 if (opts.descending) {
7311 params.descending = true;
7312 }
7313
7314 /* istanbul ignore if */
7315 if (opts.update_seq) {
7316 params.update_seq = true;
7317 }
7318
7319 if ('heartbeat' in opts) {
7320 // If the heartbeat value is false, it disables the default heartbeat
7321 if (opts.heartbeat) {
7322 params.heartbeat = opts.heartbeat;
7323 }
7324 }
7325
7326 if (opts.filter && typeof opts.filter === 'string') {
7327 params.filter = opts.filter;
7328 }
7329
7330 if (opts.view && typeof opts.view === 'string') {
7331 params.filter = '_view';
7332 params.view = opts.view;
7333 }
7334
7335 // If opts.query_params exists, pass it through to the changes request.
7336 // These parameters may be used by the filter on the source database.
7337 if (opts.query_params && typeof opts.query_params === 'object') {
7338 for (var param_name in opts.query_params) {
7339 /* istanbul ignore else */
7340 if (opts.query_params.hasOwnProperty(param_name)) {
7341 params[param_name] = opts.query_params[param_name];
7342 }
7343 }
7344 }
7345
7346 var method = 'GET';
7347 var body;
7348
7349 if (opts.doc_ids) {
7350 // set this automagically for the user; it's annoying that couchdb
7351 // requires both a "filter" and a "doc_ids" param.
7352 params.filter = '_doc_ids';
7353 method = 'POST';
7354 body = {doc_ids: opts.doc_ids };
7355 }
7356 /* istanbul ignore next */
7357 else if (opts.selector) {
7358 // set this automagically for the user, similar to above
7359 params.filter = '_selector';
7360 method = 'POST';
7361 body = {selector: opts.selector };
7362 }
7363
7364 var controller = new AbortController();
7365 var lastFetchedSeq;
7366
7367 // Get all the changes starting wtih the one immediately after the
7368 // sequence number given by since.
7369 var fetchData = function (since, callback) {
7370 if (opts.aborted) {
7371 return;
7372 }
7373 params.since = since;
7374 // "since" can be any kind of json object in Cloudant/CouchDB 2.x
7375 /* istanbul ignore next */
7376 if (typeof params.since === "object") {
7377 params.since = JSON.stringify(params.since);
7378 }
7379
7380 if (opts.descending) {
7381 if (limit) {
7382 params.limit = leftToFetch;
7383 }
7384 } else {
7385 params.limit = (!limit || leftToFetch > batchSize) ?
7386 batchSize : leftToFetch;
7387 }
7388
7389 // Set the options for the ajax call
7390 var url = genDBUrl(host, '_changes' + paramsToStr(params));
7391 var fetchOpts = {
7392 signal: controller.signal,
7393 method: method,
7394 body: JSON.stringify(body)
7395 };
7396 lastFetchedSeq = since;
7397
7398 /* istanbul ignore if */
7399 if (opts.aborted) {
7400 return;
7401 }
7402
7403 // Get the changes
7404 setup().then(function () {
7405 return fetchJSON(url, fetchOpts, callback);
7406 }).catch(callback);
7407 };
7408
7409 // If opts.since exists, get all the changes from the sequence
7410 // number given by opts.since. Otherwise, get all the changes
7411 // from the sequence number 0.
7412 var results = {results: []};
7413
7414 var fetched = function (err, res$$1) {
7415 if (opts.aborted) {
7416 return;
7417 }
7418 var raw_results_length = 0;
7419 // If the result of the ajax call (res) contains changes (res.results)
7420 if (res$$1 && res$$1.results) {
7421 raw_results_length = res$$1.results.length;
7422 results.last_seq = res$$1.last_seq;
7423 var pending = null;
7424 var lastSeq = null;
7425 // Attach 'pending' property if server supports it (CouchDB 2.0+)
7426 /* istanbul ignore if */
7427 if (typeof res$$1.pending === 'number') {
7428 pending = res$$1.pending;
7429 }
7430 if (typeof results.last_seq === 'string' || typeof results.last_seq === 'number') {
7431 lastSeq = results.last_seq;
7432 }
7433 // For each change
7434 var req = {};
7435 req.query = opts.query_params;
7436 res$$1.results = res$$1.results.filter(function (c) {
7437 leftToFetch--;
7438 var ret = filterChange(opts)(c);
7439 if (ret) {
7440 if (opts.include_docs && opts.attachments && opts.binary) {
7441 readAttachmentsAsBlobOrBuffer(c);
7442 }
7443 if (opts.return_docs) {
7444 results.results.push(c);
7445 }
7446 opts.onChange(c, pending, lastSeq);
7447 }
7448 return ret;
7449 });
7450 } else if (err) {
7451 // In case of an error, stop listening for changes and call
7452 // opts.complete
7453 opts.aborted = true;
7454 opts.complete(err);
7455 return;
7456 }
7457
7458 // The changes feed may have timed out with no results
7459 // if so reuse last update sequence
7460 if (res$$1 && res$$1.last_seq) {
7461 lastFetchedSeq = res$$1.last_seq;
7462 }
7463
7464 var finished = (limit && leftToFetch <= 0) ||
7465 (res$$1 && raw_results_length < batchSize) ||
7466 (opts.descending);
7467
7468 if ((opts.continuous && !(limit && leftToFetch <= 0)) || !finished) {
7469 // Queue a call to fetch again with the newest sequence number
7470 nextTick(function () { fetchData(lastFetchedSeq, fetched); });
7471 } else {
7472 // We're done, call the callback
7473 opts.complete(null, results);
7474 }
7475 };
7476
7477 fetchData(opts.since || 0, fetched);
7478
7479 // Return a method to cancel this method from processing any more
7480 return {
7481 cancel: function () {
7482 opts.aborted = true;
7483 controller.abort();
7484 }
7485 };
7486 };
7487
7488 // Given a set of document/revision IDs (given by req), tets the subset of
7489 // those that do NOT correspond to revisions stored in the database.
7490 // See http://wiki.apache.org/couchdb/HttpPostRevsDiff
7491 api.revsDiff = adapterFun$$1('revsDiff', function (req, opts, callback) {
7492 // If no options were given, set the callback to be the second parameter
7493 if (typeof opts === 'function') {
7494 callback = opts;
7495 opts = {};
7496 }
7497
7498 // Get the missing document/revision IDs
7499 fetchJSON(genDBUrl(host, '_revs_diff'), {
7500 method: 'POST',
7501 body: JSON.stringify(req)
7502 }, callback).catch(callback);
7503 });
7504
7505 api._close = function (callback) {
7506 callback();
7507 };
7508
7509 api._destroy = function (options, callback) {
7510 fetchJSON(genDBUrl(host, ''), {method: 'DELETE'}).then(function (json) {
7511 callback(null, json);
7512 }).catch(function (err) {
7513 /* istanbul ignore if */
7514 if (err.status === 404) {
7515 callback(null, {ok: true});
7516 } else {
7517 callback(err);
7518 }
7519 });
7520 };
7521}
7522
7523// HttpPouch is a valid adapter.
7524HttpPouch.valid = function () {
7525 return true;
7526};
7527
7528function HttpPouch$1 (PouchDB) {
7529 PouchDB.adapter('http', HttpPouch, false);
7530 PouchDB.adapter('https', HttpPouch, false);
7531}
7532
7533function QueryParseError(message) {
7534 this.status = 400;
7535 this.name = 'query_parse_error';
7536 this.message = message;
7537 this.error = true;
7538 try {
7539 Error.captureStackTrace(this, QueryParseError);
7540 } catch (e) {}
7541}
7542
7543inherits(QueryParseError, Error);
7544
7545function NotFoundError$1(message) {
7546 this.status = 404;
7547 this.name = 'not_found';
7548 this.message = message;
7549 this.error = true;
7550 try {
7551 Error.captureStackTrace(this, NotFoundError$1);
7552 } catch (e) {}
7553}
7554
7555inherits(NotFoundError$1, Error);
7556
7557function BuiltInError(message) {
7558 this.status = 500;
7559 this.name = 'invalid_value';
7560 this.message = message;
7561 this.error = true;
7562 try {
7563 Error.captureStackTrace(this, BuiltInError);
7564 } catch (e) {}
7565}
7566
7567inherits(BuiltInError, Error);
7568
7569function promisedCallback(promise, callback) {
7570 if (callback) {
7571 promise.then(function (res$$1) {
7572 nextTick(function () {
7573 callback(null, res$$1);
7574 });
7575 }, function (reason) {
7576 nextTick(function () {
7577 callback(reason);
7578 });
7579 });
7580 }
7581 return promise;
7582}
7583
7584function callbackify(fun) {
7585 return getArguments(function (args) {
7586 var cb = args.pop();
7587 var promise = fun.apply(this, args);
7588 if (typeof cb === 'function') {
7589 promisedCallback(promise, cb);
7590 }
7591 return promise;
7592 });
7593}
7594
7595// Promise finally util similar to Q.finally
7596function fin(promise, finalPromiseFactory) {
7597 return promise.then(function (res$$1) {
7598 return finalPromiseFactory().then(function () {
7599 return res$$1;
7600 });
7601 }, function (reason) {
7602 return finalPromiseFactory().then(function () {
7603 throw reason;
7604 });
7605 });
7606}
7607
7608function sequentialize(queue, promiseFactory) {
7609 return function () {
7610 var args = arguments;
7611 var that = this;
7612 return queue.add(function () {
7613 return promiseFactory.apply(that, args);
7614 });
7615 };
7616}
7617
7618// uniq an array of strings, order not guaranteed
7619// similar to underscore/lodash _.uniq
7620function uniq(arr) {
7621 var theSet = new ExportedSet(arr);
7622 var result = new Array(theSet.size);
7623 var index = -1;
7624 theSet.forEach(function (value) {
7625 result[++index] = value;
7626 });
7627 return result;
7628}
7629
7630function mapToKeysArray(map) {
7631 var result = new Array(map.size);
7632 var index = -1;
7633 map.forEach(function (value, key) {
7634 result[++index] = key;
7635 });
7636 return result;
7637}
7638
7639function createBuiltInError(name) {
7640 var message = 'builtin ' + name +
7641 ' function requires map values to be numbers' +
7642 ' or number arrays';
7643 return new BuiltInError(message);
7644}
7645
7646function sum(values) {
7647 var result = 0;
7648 for (var i = 0, len = values.length; i < len; i++) {
7649 var num = values[i];
7650 if (typeof num !== 'number') {
7651 if (Array.isArray(num)) {
7652 // lists of numbers are also allowed, sum them separately
7653 result = typeof result === 'number' ? [result] : result;
7654 for (var j = 0, jLen = num.length; j < jLen; j++) {
7655 var jNum = num[j];
7656 if (typeof jNum !== 'number') {
7657 throw createBuiltInError('_sum');
7658 } else if (typeof result[j] === 'undefined') {
7659 result.push(jNum);
7660 } else {
7661 result[j] += jNum;
7662 }
7663 }
7664 } else { // not array/number
7665 throw createBuiltInError('_sum');
7666 }
7667 } else if (typeof result === 'number') {
7668 result += num;
7669 } else { // add number to array
7670 result[0] += num;
7671 }
7672 }
7673 return result;
7674}
7675
7676// Inside of 'vm' for Node, we need a way to translate a pseudo-error
7677// back into a real error once it's out of the VM.
7678function createBuiltInErrorInVm(name) {
7679 return {
7680 builtInError: true,
7681 name: name
7682 };
7683}
7684
7685function convertToTrueError(err) {
7686 return createBuiltInError(err.name);
7687}
7688
7689function isBuiltInError(obj$$1) {
7690 return obj$$1 && obj$$1.builtInError;
7691}
7692
7693// All of this vm hullaballoo is to be able to run arbitrary code in a sandbox
7694// for security reasons.
7695function evalFunctionInVm(func, emit) {
7696 return function (arg1, arg2, arg3) {
7697 var code = '(function() {"use strict";' +
7698 'var createBuiltInError = ' + createBuiltInErrorInVm.toString() + ';' +
7699 'var sum = ' + sum.toString() + ';' +
7700 'var log = function () {};' +
7701 'var isArray = Array.isArray;' +
7702 'var toJSON = JSON.parse;' +
7703 'var __emitteds__ = [];' +
7704 'var emit = function (key, value) {__emitteds__.push([key, value]);};' +
7705 'var __result__ = (' +
7706 func.replace(/;\s*$/, '') + ')' + '(' +
7707 JSON.stringify(arg1) + ',' +
7708 JSON.stringify(arg2) + ',' +
7709 JSON.stringify(arg3) + ');' +
7710 'return {result: __result__, emitteds: __emitteds__};' +
7711 '})()';
7712
7713 var output = vm.runInNewContext(code);
7714
7715 output.emitteds.forEach(function (emitted) {
7716 emit(emitted[0], emitted[1]);
7717 });
7718 if (isBuiltInError(output.result)) {
7719 output.result = convertToTrueError(output.result);
7720 }
7721 return output.result;
7722 };
7723}
7724
7725var log = guardedConsole.bind(null, 'log');
7726var toJSON = JSON.parse;
7727
7728// The "stringify, then execute in a VM" strategy totally breaks Istanbul due
7729// to missing __coverage global objects. As a solution, export different
7730// code during coverage testing and during regular execution.
7731// Note that this doesn't get shipped to consumers because Rollup replaces it
7732// with rollup-plugin-replace, so false is replaced with `false`
7733var evalFunc;
7734/* istanbul ignore else */
7735{
7736 evalFunc = evalFunctionInVm;
7737}
7738
7739var evalFunction = evalFunc;
7740
7741/*
7742 * Simple task queue to sequentialize actions. Assumes
7743 * callbacks will eventually fire (once).
7744 */
7745
7746
7747function TaskQueue$1() {
7748 this.promise = new Promise(function (fulfill) {fulfill(); });
7749}
7750TaskQueue$1.prototype.add = function (promiseFactory) {
7751 this.promise = this.promise.catch(function () {
7752 // just recover
7753 }).then(function () {
7754 return promiseFactory();
7755 });
7756 return this.promise;
7757};
7758TaskQueue$1.prototype.finish = function () {
7759 return this.promise;
7760};
7761
7762function stringify(input) {
7763 if (!input) {
7764 return 'undefined'; // backwards compat for empty reduce
7765 }
7766 // for backwards compat with mapreduce, functions/strings are stringified
7767 // as-is. everything else is JSON-stringified.
7768 switch (typeof input) {
7769 case 'function':
7770 // e.g. a mapreduce map
7771 return input.toString();
7772 case 'string':
7773 // e.g. a mapreduce built-in _reduce function
7774 return input.toString();
7775 default:
7776 // e.g. a JSON object in the case of mango queries
7777 return JSON.stringify(input);
7778 }
7779}
7780
7781/* create a string signature for a view so we can cache it and uniq it */
7782function createViewSignature(mapFun, reduceFun) {
7783 // the "undefined" part is for backwards compatibility
7784 return stringify(mapFun) + stringify(reduceFun) + 'undefined';
7785}
7786
7787function createView(sourceDB, viewName, mapFun, reduceFun, temporary, localDocName) {
7788 var viewSignature = createViewSignature(mapFun, reduceFun);
7789
7790 var cachedViews;
7791 if (!temporary) {
7792 // cache this to ensure we don't try to update the same view twice
7793 cachedViews = sourceDB._cachedViews = sourceDB._cachedViews || {};
7794 if (cachedViews[viewSignature]) {
7795 return cachedViews[viewSignature];
7796 }
7797 }
7798
7799 var promiseForView = sourceDB.info().then(function (info) {
7800
7801 var depDbName = info.db_name + '-mrview-' +
7802 (temporary ? 'temp' : stringMd5(viewSignature));
7803
7804 // save the view name in the source db so it can be cleaned up if necessary
7805 // (e.g. when the _design doc is deleted, remove all associated view data)
7806 function diffFunction(doc) {
7807 doc.views = doc.views || {};
7808 var fullViewName = viewName;
7809 if (fullViewName.indexOf('/') === -1) {
7810 fullViewName = viewName + '/' + viewName;
7811 }
7812 var depDbs = doc.views[fullViewName] = doc.views[fullViewName] || {};
7813 /* istanbul ignore if */
7814 if (depDbs[depDbName]) {
7815 return; // no update necessary
7816 }
7817 depDbs[depDbName] = true;
7818 return doc;
7819 }
7820 return upsert(sourceDB, '_local/' + localDocName, diffFunction).then(function () {
7821 return sourceDB.registerDependentDatabase(depDbName).then(function (res$$1) {
7822 var db = res$$1.db;
7823 db.auto_compaction = true;
7824 var view = {
7825 name: depDbName,
7826 db: db,
7827 sourceDB: sourceDB,
7828 adapter: sourceDB.adapter,
7829 mapFun: mapFun,
7830 reduceFun: reduceFun
7831 };
7832 return view.db.get('_local/lastSeq').catch(function (err) {
7833 /* istanbul ignore if */
7834 if (err.status !== 404) {
7835 throw err;
7836 }
7837 }).then(function (lastSeqDoc) {
7838 view.seq = lastSeqDoc ? lastSeqDoc.seq : 0;
7839 if (cachedViews) {
7840 view.db.once('destroyed', function () {
7841 delete cachedViews[viewSignature];
7842 });
7843 }
7844 return view;
7845 });
7846 });
7847 });
7848 });
7849
7850 if (cachedViews) {
7851 cachedViews[viewSignature] = promiseForView;
7852 }
7853 return promiseForView;
7854}
7855
7856var persistentQueues = {};
7857var tempViewQueue = new TaskQueue$1();
7858var CHANGES_BATCH_SIZE$1 = 50;
7859
7860function parseViewName(name) {
7861 // can be either 'ddocname/viewname' or just 'viewname'
7862 // (where the ddoc name is the same)
7863 return name.indexOf('/') === -1 ? [name, name] : name.split('/');
7864}
7865
7866function isGenOne(changes) {
7867 // only return true if the current change is 1-
7868 // and there are no other leafs
7869 return changes.length === 1 && /^1-/.test(changes[0].rev);
7870}
7871
7872function emitError(db, e) {
7873 try {
7874 db.emit('error', e);
7875 } catch (err) {
7876 guardedConsole('error',
7877 'The user\'s map/reduce function threw an uncaught error.\n' +
7878 'You can debug this error by doing:\n' +
7879 'myDatabase.on(\'error\', function (err) { debugger; });\n' +
7880 'Please double-check your map/reduce function.');
7881 guardedConsole('error', e);
7882 }
7883}
7884
7885/**
7886 * Returns an "abstract" mapreduce object of the form:
7887 *
7888 * {
7889 * query: queryFun,
7890 * viewCleanup: viewCleanupFun
7891 * }
7892 *
7893 * Arguments are:
7894 *
7895 * localDoc: string
7896 * This is for the local doc that gets saved in order to track the
7897 * "dependent" DBs and clean them up for viewCleanup. It should be
7898 * unique, so that indexer plugins don't collide with each other.
7899 * mapper: function (mapFunDef, emit)
7900 * Returns a map function based on the mapFunDef, which in the case of
7901 * normal map/reduce is just the de-stringified function, but may be
7902 * something else, such as an object in the case of pouchdb-find.
7903 * reducer: function (reduceFunDef)
7904 * Ditto, but for reducing. Modules don't have to support reducing
7905 * (e.g. pouchdb-find).
7906 * ddocValidator: function (ddoc, viewName)
7907 * Throws an error if the ddoc or viewName is not valid.
7908 * This could be a way to communicate to the user that the configuration for the
7909 * indexer is invalid.
7910 */
7911function createAbstractMapReduce(localDocName, mapper, reducer, ddocValidator) {
7912
7913 function tryMap(db, fun, doc) {
7914 // emit an event if there was an error thrown by a map function.
7915 // putting try/catches in a single function also avoids deoptimizations.
7916 try {
7917 fun(doc);
7918 } catch (e) {
7919 emitError(db, e);
7920 }
7921 }
7922
7923 function tryReduce(db, fun, keys, values, rereduce) {
7924 // same as above, but returning the result or an error. there are two separate
7925 // functions to avoid extra memory allocations since the tryCode() case is used
7926 // for custom map functions (common) vs this function, which is only used for
7927 // custom reduce functions (rare)
7928 try {
7929 return {output : fun(keys, values, rereduce)};
7930 } catch (e) {
7931 emitError(db, e);
7932 return {error: e};
7933 }
7934 }
7935
7936 function sortByKeyThenValue(x, y) {
7937 var keyCompare = collate(x.key, y.key);
7938 return keyCompare !== 0 ? keyCompare : collate(x.value, y.value);
7939 }
7940
7941 function sliceResults(results, limit, skip) {
7942 skip = skip || 0;
7943 if (typeof limit === 'number') {
7944 return results.slice(skip, limit + skip);
7945 } else if (skip > 0) {
7946 return results.slice(skip);
7947 }
7948 return results;
7949 }
7950
7951 function rowToDocId(row) {
7952 var val = row.value;
7953 // Users can explicitly specify a joined doc _id, or it
7954 // defaults to the doc _id that emitted the key/value.
7955 var docId = (val && typeof val === 'object' && val._id) || row.id;
7956 return docId;
7957 }
7958
7959 function readAttachmentsAsBlobOrBuffer(res$$1) {
7960 res$$1.rows.forEach(function (row) {
7961 var atts = row.doc && row.doc._attachments;
7962 if (!atts) {
7963 return;
7964 }
7965 Object.keys(atts).forEach(function (filename) {
7966 var att = atts[filename];
7967 atts[filename].data = b64ToBluffer(att.data, att.content_type);
7968 });
7969 });
7970 }
7971
7972 function postprocessAttachments(opts) {
7973 return function (res$$1) {
7974 if (opts.include_docs && opts.attachments && opts.binary) {
7975 readAttachmentsAsBlobOrBuffer(res$$1);
7976 }
7977 return res$$1;
7978 };
7979 }
7980
7981 function addHttpParam(paramName, opts, params, asJson) {
7982 // add an http param from opts to params, optionally json-encoded
7983 var val = opts[paramName];
7984 if (typeof val !== 'undefined') {
7985 if (asJson) {
7986 val = encodeURIComponent(JSON.stringify(val));
7987 }
7988 params.push(paramName + '=' + val);
7989 }
7990 }
7991
7992 function coerceInteger(integerCandidate) {
7993 if (typeof integerCandidate !== 'undefined') {
7994 var asNumber = Number(integerCandidate);
7995 // prevents e.g. '1foo' or '1.1' being coerced to 1
7996 if (!isNaN(asNumber) && asNumber === parseInt(integerCandidate, 10)) {
7997 return asNumber;
7998 } else {
7999 return integerCandidate;
8000 }
8001 }
8002 }
8003
8004 function coerceOptions(opts) {
8005 opts.group_level = coerceInteger(opts.group_level);
8006 opts.limit = coerceInteger(opts.limit);
8007 opts.skip = coerceInteger(opts.skip);
8008 return opts;
8009 }
8010
8011 function checkPositiveInteger(number) {
8012 if (number) {
8013 if (typeof number !== 'number') {
8014 return new QueryParseError('Invalid value for integer: "' +
8015 number + '"');
8016 }
8017 if (number < 0) {
8018 return new QueryParseError('Invalid value for positive integer: ' +
8019 '"' + number + '"');
8020 }
8021 }
8022 }
8023
8024 function checkQueryParseError(options, fun) {
8025 var startkeyName = options.descending ? 'endkey' : 'startkey';
8026 var endkeyName = options.descending ? 'startkey' : 'endkey';
8027
8028 if (typeof options[startkeyName] !== 'undefined' &&
8029 typeof options[endkeyName] !== 'undefined' &&
8030 collate(options[startkeyName], options[endkeyName]) > 0) {
8031 throw new QueryParseError('No rows can match your key range, ' +
8032 'reverse your start_key and end_key or set {descending : true}');
8033 } else if (fun.reduce && options.reduce !== false) {
8034 if (options.include_docs) {
8035 throw new QueryParseError('{include_docs:true} is invalid for reduce');
8036 } else if (options.keys && options.keys.length > 1 &&
8037 !options.group && !options.group_level) {
8038 throw new QueryParseError('Multi-key fetches for reduce views must use ' +
8039 '{group: true}');
8040 }
8041 }
8042 ['group_level', 'limit', 'skip'].forEach(function (optionName) {
8043 var error = checkPositiveInteger(options[optionName]);
8044 if (error) {
8045 throw error;
8046 }
8047 });
8048 }
8049
8050 function httpQuery(db, fun, opts) {
8051 // List of parameters to add to the PUT request
8052 var params = [];
8053 var body;
8054 var method = 'GET';
8055 var ok, status;
8056
8057 // If opts.reduce exists and is defined, then add it to the list
8058 // of parameters.
8059 // If reduce=false then the results are that of only the map function
8060 // not the final result of map and reduce.
8061 addHttpParam('reduce', opts, params);
8062 addHttpParam('include_docs', opts, params);
8063 addHttpParam('attachments', opts, params);
8064 addHttpParam('limit', opts, params);
8065 addHttpParam('descending', opts, params);
8066 addHttpParam('group', opts, params);
8067 addHttpParam('group_level', opts, params);
8068 addHttpParam('skip', opts, params);
8069 addHttpParam('stale', opts, params);
8070 addHttpParam('conflicts', opts, params);
8071 addHttpParam('startkey', opts, params, true);
8072 addHttpParam('start_key', opts, params, true);
8073 addHttpParam('endkey', opts, params, true);
8074 addHttpParam('end_key', opts, params, true);
8075 addHttpParam('inclusive_end', opts, params);
8076 addHttpParam('key', opts, params, true);
8077 addHttpParam('update_seq', opts, params);
8078
8079 // Format the list of parameters into a valid URI query string
8080 params = params.join('&');
8081 params = params === '' ? '' : '?' + params;
8082
8083 // If keys are supplied, issue a POST to circumvent GET query string limits
8084 // see http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options
8085 if (typeof opts.keys !== 'undefined') {
8086 var MAX_URL_LENGTH = 2000;
8087 // according to http://stackoverflow.com/a/417184/680742,
8088 // the de facto URL length limit is 2000 characters
8089
8090 var keysAsString =
8091 'keys=' + encodeURIComponent(JSON.stringify(opts.keys));
8092 if (keysAsString.length + params.length + 1 <= MAX_URL_LENGTH) {
8093 // If the keys are short enough, do a GET. we do this to work around
8094 // Safari not understanding 304s on POSTs (see pouchdb/pouchdb#1239)
8095 params += (params[0] === '?' ? '&' : '?') + keysAsString;
8096 } else {
8097 method = 'POST';
8098 if (typeof fun === 'string') {
8099 body = {keys: opts.keys};
8100 } else { // fun is {map : mapfun}, so append to this
8101 fun.keys = opts.keys;
8102 }
8103 }
8104 }
8105
8106 // We are referencing a query defined in the design doc
8107 if (typeof fun === 'string') {
8108 var parts = parseViewName(fun);
8109 return db.fetch('_design/' + parts[0] + '/_view/' + parts[1] + params, {
8110 headers: new Headers({'Content-Type': 'application/json'}),
8111 method: method,
8112 body: JSON.stringify(body)
8113 }).then(function (response) {
8114 ok = response.ok;
8115 status = response.status;
8116 return response.json();
8117 }).then(function (result) {
8118 if (!ok) {
8119 result.status = status;
8120 throw generateErrorFromResponse(result);
8121 }
8122 // fail the entire request if the result contains an error
8123 result.rows.forEach(function (row) {
8124 /* istanbul ignore if */
8125 if (row.value && row.value.error && row.value.error === "builtin_reduce_error") {
8126 throw new Error(row.reason);
8127 }
8128 });
8129 return result;
8130 }).then(postprocessAttachments(opts));
8131 }
8132
8133 // We are using a temporary view, terrible for performance, good for testing
8134 body = body || {};
8135 Object.keys(fun).forEach(function (key) {
8136 if (Array.isArray(fun[key])) {
8137 body[key] = fun[key];
8138 } else {
8139 body[key] = fun[key].toString();
8140 }
8141 });
8142
8143 return db.fetch('_temp_view' + params, {
8144 headers: new Headers({'Content-Type': 'application/json'}),
8145 method: 'POST',
8146 body: JSON.stringify(body)
8147 }).then(function (response) {
8148 ok = response.ok;
8149 status = response.status;
8150 return response.json();
8151 }).then(function (result) {
8152 if (!ok) {
8153 result.status = status;
8154 throw generateErrorFromResponse(result);
8155 }
8156 return result;
8157 }).then(postprocessAttachments(opts));
8158 }
8159
8160 // custom adapters can define their own api._query
8161 // and override the default behavior
8162 /* istanbul ignore next */
8163 function customQuery(db, fun, opts) {
8164 return new Promise(function (resolve, reject) {
8165 db._query(fun, opts, function (err, res$$1) {
8166 if (err) {
8167 return reject(err);
8168 }
8169 resolve(res$$1);
8170 });
8171 });
8172 }
8173
8174 // custom adapters can define their own api._viewCleanup
8175 // and override the default behavior
8176 /* istanbul ignore next */
8177 function customViewCleanup(db) {
8178 return new Promise(function (resolve, reject) {
8179 db._viewCleanup(function (err, res$$1) {
8180 if (err) {
8181 return reject(err);
8182 }
8183 resolve(res$$1);
8184 });
8185 });
8186 }
8187
8188 function defaultsTo(value) {
8189 return function (reason) {
8190 /* istanbul ignore else */
8191 if (reason.status === 404) {
8192 return value;
8193 } else {
8194 throw reason;
8195 }
8196 };
8197 }
8198
8199 // returns a promise for a list of docs to update, based on the input docId.
8200 // the order doesn't matter, because post-3.2.0, bulkDocs
8201 // is an atomic operation in all three adapters.
8202 function getDocsToPersist(docId, view, docIdsToChangesAndEmits) {
8203 var metaDocId = '_local/doc_' + docId;
8204 var defaultMetaDoc = {_id: metaDocId, keys: []};
8205 var docData = docIdsToChangesAndEmits.get(docId);
8206 var indexableKeysToKeyValues = docData[0];
8207 var changes = docData[1];
8208
8209 function getMetaDoc() {
8210 if (isGenOne(changes)) {
8211 // generation 1, so we can safely assume initial state
8212 // for performance reasons (avoids unnecessary GETs)
8213 return Promise.resolve(defaultMetaDoc);
8214 }
8215 return view.db.get(metaDocId).catch(defaultsTo(defaultMetaDoc));
8216 }
8217
8218 function getKeyValueDocs(metaDoc) {
8219 if (!metaDoc.keys.length) {
8220 // no keys, no need for a lookup
8221 return Promise.resolve({rows: []});
8222 }
8223 return view.db.allDocs({
8224 keys: metaDoc.keys,
8225 include_docs: true
8226 });
8227 }
8228
8229 function processKeyValueDocs(metaDoc, kvDocsRes) {
8230 var kvDocs = [];
8231 var oldKeys = new ExportedSet();
8232
8233 for (var i = 0, len = kvDocsRes.rows.length; i < len; i++) {
8234 var row = kvDocsRes.rows[i];
8235 var doc = row.doc;
8236 if (!doc) { // deleted
8237 continue;
8238 }
8239 kvDocs.push(doc);
8240 oldKeys.add(doc._id);
8241 doc._deleted = !indexableKeysToKeyValues.has(doc._id);
8242 if (!doc._deleted) {
8243 var keyValue = indexableKeysToKeyValues.get(doc._id);
8244 if ('value' in keyValue) {
8245 doc.value = keyValue.value;
8246 }
8247 }
8248 }
8249 var newKeys = mapToKeysArray(indexableKeysToKeyValues);
8250 newKeys.forEach(function (key) {
8251 if (!oldKeys.has(key)) {
8252 // new doc
8253 var kvDoc = {
8254 _id: key
8255 };
8256 var keyValue = indexableKeysToKeyValues.get(key);
8257 if ('value' in keyValue) {
8258 kvDoc.value = keyValue.value;
8259 }
8260 kvDocs.push(kvDoc);
8261 }
8262 });
8263 metaDoc.keys = uniq(newKeys.concat(metaDoc.keys));
8264 kvDocs.push(metaDoc);
8265
8266 return kvDocs;
8267 }
8268
8269 return getMetaDoc().then(function (metaDoc) {
8270 return getKeyValueDocs(metaDoc).then(function (kvDocsRes) {
8271 return processKeyValueDocs(metaDoc, kvDocsRes);
8272 });
8273 });
8274 }
8275
8276 // updates all emitted key/value docs and metaDocs in the mrview database
8277 // for the given batch of documents from the source database
8278 function saveKeyValues(view, docIdsToChangesAndEmits, seq) {
8279 var seqDocId = '_local/lastSeq';
8280 return view.db.get(seqDocId)
8281 .catch(defaultsTo({_id: seqDocId, seq: 0}))
8282 .then(function (lastSeqDoc) {
8283 var docIds = mapToKeysArray(docIdsToChangesAndEmits);
8284 return Promise.all(docIds.map(function (docId) {
8285 return getDocsToPersist(docId, view, docIdsToChangesAndEmits);
8286 })).then(function (listOfDocsToPersist) {
8287 var docsToPersist = flatten(listOfDocsToPersist);
8288 lastSeqDoc.seq = seq;
8289 docsToPersist.push(lastSeqDoc);
8290 // write all docs in a single operation, update the seq once
8291 return view.db.bulkDocs({docs : docsToPersist});
8292 });
8293 });
8294 }
8295
8296 function getQueue(view) {
8297 var viewName = typeof view === 'string' ? view : view.name;
8298 var queue = persistentQueues[viewName];
8299 if (!queue) {
8300 queue = persistentQueues[viewName] = new TaskQueue$1();
8301 }
8302 return queue;
8303 }
8304
8305 function updateView(view) {
8306 return sequentialize(getQueue(view), function () {
8307 return updateViewInQueue(view);
8308 })();
8309 }
8310
8311 function updateViewInQueue(view) {
8312 // bind the emit function once
8313 var mapResults;
8314 var doc;
8315
8316 function emit(key, value) {
8317 var output = {id: doc._id, key: normalizeKey(key)};
8318 // Don't explicitly store the value unless it's defined and non-null.
8319 // This saves on storage space, because often people don't use it.
8320 if (typeof value !== 'undefined' && value !== null) {
8321 output.value = normalizeKey(value);
8322 }
8323 mapResults.push(output);
8324 }
8325
8326 var mapFun = mapper(view.mapFun, emit);
8327
8328 var currentSeq = view.seq || 0;
8329
8330 function processChange(docIdsToChangesAndEmits, seq) {
8331 return function () {
8332 return saveKeyValues(view, docIdsToChangesAndEmits, seq);
8333 };
8334 }
8335
8336 var queue = new TaskQueue$1();
8337
8338 function processNextBatch() {
8339 return view.sourceDB.changes({
8340 return_docs: true,
8341 conflicts: true,
8342 include_docs: true,
8343 style: 'all_docs',
8344 since: currentSeq,
8345 limit: CHANGES_BATCH_SIZE$1
8346 }).then(processBatch);
8347 }
8348
8349 function processBatch(response) {
8350 var results = response.results;
8351 if (!results.length) {
8352 return;
8353 }
8354 var docIdsToChangesAndEmits = createDocIdsToChangesAndEmits(results);
8355 queue.add(processChange(docIdsToChangesAndEmits, currentSeq));
8356 if (results.length < CHANGES_BATCH_SIZE$1) {
8357 return;
8358 }
8359 return processNextBatch();
8360 }
8361
8362 function createDocIdsToChangesAndEmits(results) {
8363 var docIdsToChangesAndEmits = new ExportedMap();
8364 for (var i = 0, len = results.length; i < len; i++) {
8365 var change = results[i];
8366 if (change.doc._id[0] !== '_') {
8367 mapResults = [];
8368 doc = change.doc;
8369
8370 if (!doc._deleted) {
8371 tryMap(view.sourceDB, mapFun, doc);
8372 }
8373 mapResults.sort(sortByKeyThenValue);
8374
8375 var indexableKeysToKeyValues = createIndexableKeysToKeyValues(mapResults);
8376 docIdsToChangesAndEmits.set(change.doc._id, [
8377 indexableKeysToKeyValues,
8378 change.changes
8379 ]);
8380 }
8381 currentSeq = change.seq;
8382 }
8383 return docIdsToChangesAndEmits;
8384 }
8385
8386 function createIndexableKeysToKeyValues(mapResults) {
8387 var indexableKeysToKeyValues = new ExportedMap();
8388 var lastKey;
8389 for (var i = 0, len = mapResults.length; i < len; i++) {
8390 var emittedKeyValue = mapResults[i];
8391 var complexKey = [emittedKeyValue.key, emittedKeyValue.id];
8392 if (i > 0 && collate(emittedKeyValue.key, lastKey) === 0) {
8393 complexKey.push(i); // dup key+id, so make it unique
8394 }
8395 indexableKeysToKeyValues.set(toIndexableString(complexKey), emittedKeyValue);
8396 lastKey = emittedKeyValue.key;
8397 }
8398 return indexableKeysToKeyValues;
8399 }
8400
8401 return processNextBatch().then(function () {
8402 return queue.finish();
8403 }).then(function () {
8404 view.seq = currentSeq;
8405 });
8406 }
8407
8408 function reduceView(view, results, options) {
8409 if (options.group_level === 0) {
8410 delete options.group_level;
8411 }
8412
8413 var shouldGroup = options.group || options.group_level;
8414
8415 var reduceFun = reducer(view.reduceFun);
8416
8417 var groups = [];
8418 var lvl = isNaN(options.group_level) ? Number.POSITIVE_INFINITY :
8419 options.group_level;
8420 results.forEach(function (e) {
8421 var last = groups[groups.length - 1];
8422 var groupKey = shouldGroup ? e.key : null;
8423
8424 // only set group_level for array keys
8425 if (shouldGroup && Array.isArray(groupKey)) {
8426 groupKey = groupKey.slice(0, lvl);
8427 }
8428
8429 if (last && collate(last.groupKey, groupKey) === 0) {
8430 last.keys.push([e.key, e.id]);
8431 last.values.push(e.value);
8432 return;
8433 }
8434 groups.push({
8435 keys: [[e.key, e.id]],
8436 values: [e.value],
8437 groupKey: groupKey
8438 });
8439 });
8440 results = [];
8441 for (var i = 0, len = groups.length; i < len; i++) {
8442 var e = groups[i];
8443 var reduceTry = tryReduce(view.sourceDB, reduceFun, e.keys, e.values, false);
8444 if (reduceTry.error && reduceTry.error instanceof BuiltInError) {
8445 // CouchDB returns an error if a built-in errors out
8446 throw reduceTry.error;
8447 }
8448 results.push({
8449 // CouchDB just sets the value to null if a non-built-in errors out
8450 value: reduceTry.error ? null : reduceTry.output,
8451 key: e.groupKey
8452 });
8453 }
8454 // no total_rows/offset when reducing
8455 return {rows: sliceResults(results, options.limit, options.skip)};
8456 }
8457
8458 function queryView(view, opts) {
8459 return sequentialize(getQueue(view), function () {
8460 return queryViewInQueue(view, opts);
8461 })();
8462 }
8463
8464 function queryViewInQueue(view, opts) {
8465 var totalRows;
8466 var shouldReduce = view.reduceFun && opts.reduce !== false;
8467 var skip = opts.skip || 0;
8468 if (typeof opts.keys !== 'undefined' && !opts.keys.length) {
8469 // equivalent query
8470 opts.limit = 0;
8471 delete opts.keys;
8472 }
8473
8474 function fetchFromView(viewOpts) {
8475 viewOpts.include_docs = true;
8476 return view.db.allDocs(viewOpts).then(function (res$$1) {
8477 totalRows = res$$1.total_rows;
8478 return res$$1.rows.map(function (result) {
8479
8480 // implicit migration - in older versions of PouchDB,
8481 // we explicitly stored the doc as {id: ..., key: ..., value: ...}
8482 // this is tested in a migration test
8483 /* istanbul ignore next */
8484 if ('value' in result.doc && typeof result.doc.value === 'object' &&
8485 result.doc.value !== null) {
8486 var keys = Object.keys(result.doc.value).sort();
8487 // this detection method is not perfect, but it's unlikely the user
8488 // emitted a value which was an object with these 3 exact keys
8489 var expectedKeys = ['id', 'key', 'value'];
8490 if (!(keys < expectedKeys || keys > expectedKeys)) {
8491 return result.doc.value;
8492 }
8493 }
8494
8495 var parsedKeyAndDocId = parseIndexableString(result.doc._id);
8496 return {
8497 key: parsedKeyAndDocId[0],
8498 id: parsedKeyAndDocId[1],
8499 value: ('value' in result.doc ? result.doc.value : null)
8500 };
8501 });
8502 });
8503 }
8504
8505 function onMapResultsReady(rows) {
8506 var finalResults;
8507 if (shouldReduce) {
8508 finalResults = reduceView(view, rows, opts);
8509 } else {
8510 finalResults = {
8511 total_rows: totalRows,
8512 offset: skip,
8513 rows: rows
8514 };
8515 }
8516 /* istanbul ignore if */
8517 if (opts.update_seq) {
8518 finalResults.update_seq = view.seq;
8519 }
8520 if (opts.include_docs) {
8521 var docIds = uniq(rows.map(rowToDocId));
8522
8523 return view.sourceDB.allDocs({
8524 keys: docIds,
8525 include_docs: true,
8526 conflicts: opts.conflicts,
8527 attachments: opts.attachments,
8528 binary: opts.binary
8529 }).then(function (allDocsRes) {
8530 var docIdsToDocs = new ExportedMap();
8531 allDocsRes.rows.forEach(function (row) {
8532 docIdsToDocs.set(row.id, row.doc);
8533 });
8534 rows.forEach(function (row) {
8535 var docId = rowToDocId(row);
8536 var doc = docIdsToDocs.get(docId);
8537 if (doc) {
8538 row.doc = doc;
8539 }
8540 });
8541 return finalResults;
8542 });
8543 } else {
8544 return finalResults;
8545 }
8546 }
8547
8548 if (typeof opts.keys !== 'undefined') {
8549 var keys = opts.keys;
8550 var fetchPromises = keys.map(function (key) {
8551 var viewOpts = {
8552 startkey : toIndexableString([key]),
8553 endkey : toIndexableString([key, {}])
8554 };
8555 /* istanbul ignore if */
8556 if (opts.update_seq) {
8557 viewOpts.update_seq = true;
8558 }
8559 return fetchFromView(viewOpts);
8560 });
8561 return Promise.all(fetchPromises).then(flatten).then(onMapResultsReady);
8562 } else { // normal query, no 'keys'
8563 var viewOpts = {
8564 descending : opts.descending
8565 };
8566 /* istanbul ignore if */
8567 if (opts.update_seq) {
8568 viewOpts.update_seq = true;
8569 }
8570 var startkey;
8571 var endkey;
8572 if ('start_key' in opts) {
8573 startkey = opts.start_key;
8574 }
8575 if ('startkey' in opts) {
8576 startkey = opts.startkey;
8577 }
8578 if ('end_key' in opts) {
8579 endkey = opts.end_key;
8580 }
8581 if ('endkey' in opts) {
8582 endkey = opts.endkey;
8583 }
8584 if (typeof startkey !== 'undefined') {
8585 viewOpts.startkey = opts.descending ?
8586 toIndexableString([startkey, {}]) :
8587 toIndexableString([startkey]);
8588 }
8589 if (typeof endkey !== 'undefined') {
8590 var inclusiveEnd = opts.inclusive_end !== false;
8591 if (opts.descending) {
8592 inclusiveEnd = !inclusiveEnd;
8593 }
8594
8595 viewOpts.endkey = toIndexableString(
8596 inclusiveEnd ? [endkey, {}] : [endkey]);
8597 }
8598 if (typeof opts.key !== 'undefined') {
8599 var keyStart = toIndexableString([opts.key]);
8600 var keyEnd = toIndexableString([opts.key, {}]);
8601 if (viewOpts.descending) {
8602 viewOpts.endkey = keyStart;
8603 viewOpts.startkey = keyEnd;
8604 } else {
8605 viewOpts.startkey = keyStart;
8606 viewOpts.endkey = keyEnd;
8607 }
8608 }
8609 if (!shouldReduce) {
8610 if (typeof opts.limit === 'number') {
8611 viewOpts.limit = opts.limit;
8612 }
8613 viewOpts.skip = skip;
8614 }
8615 return fetchFromView(viewOpts).then(onMapResultsReady);
8616 }
8617 }
8618
8619 function httpViewCleanup(db) {
8620 return db.fetch('_view_cleanup', {
8621 headers: new Headers({'Content-Type': 'application/json'}),
8622 method: 'POST'
8623 }).then(function (response) {
8624 return response.json();
8625 });
8626 }
8627
8628 function localViewCleanup(db) {
8629 return db.get('_local/' + localDocName).then(function (metaDoc) {
8630 var docsToViews = new ExportedMap();
8631 Object.keys(metaDoc.views).forEach(function (fullViewName) {
8632 var parts = parseViewName(fullViewName);
8633 var designDocName = '_design/' + parts[0];
8634 var viewName = parts[1];
8635 var views = docsToViews.get(designDocName);
8636 if (!views) {
8637 views = new ExportedSet();
8638 docsToViews.set(designDocName, views);
8639 }
8640 views.add(viewName);
8641 });
8642 var opts = {
8643 keys : mapToKeysArray(docsToViews),
8644 include_docs : true
8645 };
8646 return db.allDocs(opts).then(function (res$$1) {
8647 var viewsToStatus = {};
8648 res$$1.rows.forEach(function (row) {
8649 var ddocName = row.key.substring(8); // cuts off '_design/'
8650 docsToViews.get(row.key).forEach(function (viewName) {
8651 var fullViewName = ddocName + '/' + viewName;
8652 /* istanbul ignore if */
8653 if (!metaDoc.views[fullViewName]) {
8654 // new format, without slashes, to support PouchDB 2.2.0
8655 // migration test in pouchdb's browser.migration.js verifies this
8656 fullViewName = viewName;
8657 }
8658 var viewDBNames = Object.keys(metaDoc.views[fullViewName]);
8659 // design doc deleted, or view function nonexistent
8660 var statusIsGood = row.doc && row.doc.views &&
8661 row.doc.views[viewName];
8662 viewDBNames.forEach(function (viewDBName) {
8663 viewsToStatus[viewDBName] =
8664 viewsToStatus[viewDBName] || statusIsGood;
8665 });
8666 });
8667 });
8668 var dbsToDelete = Object.keys(viewsToStatus).filter(
8669 function (viewDBName) { return !viewsToStatus[viewDBName]; });
8670 var destroyPromises = dbsToDelete.map(function (viewDBName) {
8671 return sequentialize(getQueue(viewDBName), function () {
8672 return new db.constructor(viewDBName, db.__opts).destroy();
8673 })();
8674 });
8675 return Promise.all(destroyPromises).then(function () {
8676 return {ok: true};
8677 });
8678 });
8679 }, defaultsTo({ok: true}));
8680 }
8681
8682 function queryPromised(db, fun, opts) {
8683 /* istanbul ignore next */
8684 if (typeof db._query === 'function') {
8685 return customQuery(db, fun, opts);
8686 }
8687 if (isRemote(db)) {
8688 return httpQuery(db, fun, opts);
8689 }
8690
8691 if (typeof fun !== 'string') {
8692 // temp_view
8693 checkQueryParseError(opts, fun);
8694
8695 tempViewQueue.add(function () {
8696 var createViewPromise = createView(
8697 /* sourceDB */ db,
8698 /* viewName */ 'temp_view/temp_view',
8699 /* mapFun */ fun.map,
8700 /* reduceFun */ fun.reduce,
8701 /* temporary */ true,
8702 /* localDocName */ localDocName);
8703 return createViewPromise.then(function (view) {
8704 return fin(updateView(view).then(function () {
8705 return queryView(view, opts);
8706 }), function () {
8707 return view.db.destroy();
8708 });
8709 });
8710 });
8711 return tempViewQueue.finish();
8712 } else {
8713 // persistent view
8714 var fullViewName = fun;
8715 var parts = parseViewName(fullViewName);
8716 var designDocName = parts[0];
8717 var viewName = parts[1];
8718 return db.get('_design/' + designDocName).then(function (doc) {
8719 var fun = doc.views && doc.views[viewName];
8720
8721 if (!fun) {
8722 // basic validator; it's assumed that every subclass would want this
8723 throw new NotFoundError$1('ddoc ' + doc._id + ' has no view named ' +
8724 viewName);
8725 }
8726
8727 ddocValidator(doc, viewName);
8728 checkQueryParseError(opts, fun);
8729
8730 var createViewPromise = createView(
8731 /* sourceDB */ db,
8732 /* viewName */ fullViewName,
8733 /* mapFun */ fun.map,
8734 /* reduceFun */ fun.reduce,
8735 /* temporary */ false,
8736 /* localDocName */ localDocName);
8737 return createViewPromise.then(function (view) {
8738 if (opts.stale === 'ok' || opts.stale === 'update_after') {
8739 if (opts.stale === 'update_after') {
8740 nextTick(function () {
8741 updateView(view);
8742 });
8743 }
8744 return queryView(view, opts);
8745 } else { // stale not ok
8746 return updateView(view).then(function () {
8747 return queryView(view, opts);
8748 });
8749 }
8750 });
8751 });
8752 }
8753 }
8754
8755 function abstractQuery(fun, opts, callback) {
8756 var db = this;
8757 if (typeof opts === 'function') {
8758 callback = opts;
8759 opts = {};
8760 }
8761 opts = opts ? coerceOptions(opts) : {};
8762
8763 if (typeof fun === 'function') {
8764 fun = {map : fun};
8765 }
8766
8767 var promise = Promise.resolve().then(function () {
8768 return queryPromised(db, fun, opts);
8769 });
8770 promisedCallback(promise, callback);
8771 return promise;
8772 }
8773
8774 var abstractViewCleanup = callbackify(function () {
8775 var db = this;
8776 /* istanbul ignore next */
8777 if (typeof db._viewCleanup === 'function') {
8778 return customViewCleanup(db);
8779 }
8780 if (isRemote(db)) {
8781 return httpViewCleanup(db);
8782 }
8783 return localViewCleanup(db);
8784 });
8785
8786 return {
8787 query: abstractQuery,
8788 viewCleanup: abstractViewCleanup
8789 };
8790}
8791
8792var builtInReduce = {
8793 _sum: function (keys, values) {
8794 return sum(values);
8795 },
8796
8797 _count: function (keys, values) {
8798 return values.length;
8799 },
8800
8801 _stats: function (keys, values) {
8802 // no need to implement rereduce=true, because Pouch
8803 // will never call it
8804 function sumsqr(values) {
8805 var _sumsqr = 0;
8806 for (var i = 0, len = values.length; i < len; i++) {
8807 var num = values[i];
8808 _sumsqr += (num * num);
8809 }
8810 return _sumsqr;
8811 }
8812 return {
8813 sum : sum(values),
8814 min : Math.min.apply(null, values),
8815 max : Math.max.apply(null, values),
8816 count : values.length,
8817 sumsqr : sumsqr(values)
8818 };
8819 }
8820};
8821
8822function getBuiltIn(reduceFunString) {
8823 if (/^_sum/.test(reduceFunString)) {
8824 return builtInReduce._sum;
8825 } else if (/^_count/.test(reduceFunString)) {
8826 return builtInReduce._count;
8827 } else if (/^_stats/.test(reduceFunString)) {
8828 return builtInReduce._stats;
8829 } else if (/^_/.test(reduceFunString)) {
8830 throw new Error(reduceFunString + ' is not a supported reduce function.');
8831 }
8832}
8833
8834function mapper(mapFun, emit) {
8835 // for temp_views one can use emit(doc, emit), see #38
8836 if (typeof mapFun === "function" && mapFun.length === 2) {
8837 var origMap = mapFun;
8838 return function (doc) {
8839 return origMap(doc, emit);
8840 };
8841 } else {
8842 return evalFunction(mapFun.toString(), emit);
8843 }
8844}
8845
8846function reducer(reduceFun) {
8847 var reduceFunString = reduceFun.toString();
8848 var builtIn = getBuiltIn(reduceFunString);
8849 if (builtIn) {
8850 return builtIn;
8851 } else {
8852 return evalFunction(reduceFunString);
8853 }
8854}
8855
8856function ddocValidator(ddoc, viewName) {
8857 var fun = ddoc.views && ddoc.views[viewName];
8858 if (typeof fun.map !== 'string') {
8859 throw new NotFoundError$1('ddoc ' + ddoc._id + ' has no string view named ' +
8860 viewName + ', instead found object of type: ' + typeof fun.map);
8861 }
8862}
8863
8864var localDocName = 'mrviews';
8865var abstract = createAbstractMapReduce(localDocName, mapper, reducer, ddocValidator);
8866
8867function query(fun, opts, callback) {
8868 return abstract.query.call(this, fun, opts, callback);
8869}
8870
8871function viewCleanup(callback) {
8872 return abstract.viewCleanup.call(this, callback);
8873}
8874
8875var mapreduce = {
8876 query: query,
8877 viewCleanup: viewCleanup
8878};
8879
8880function isGenOne$1(rev) {
8881 return /^1-/.test(rev);
8882}
8883
8884function fileHasChanged(localDoc, remoteDoc, filename) {
8885 return !localDoc._attachments ||
8886 !localDoc._attachments[filename] ||
8887 localDoc._attachments[filename].digest !== remoteDoc._attachments[filename].digest;
8888}
8889
8890function getDocAttachments(db, doc) {
8891 var filenames = Object.keys(doc._attachments);
8892 return Promise.all(filenames.map(function (filename) {
8893 return db.getAttachment(doc._id, filename, {rev: doc._rev});
8894 }));
8895}
8896
8897function getDocAttachmentsFromTargetOrSource(target, src, doc) {
8898 var doCheckForLocalAttachments = isRemote(src) && !isRemote(target);
8899 var filenames = Object.keys(doc._attachments);
8900
8901 if (!doCheckForLocalAttachments) {
8902 return getDocAttachments(src, doc);
8903 }
8904
8905 return target.get(doc._id).then(function (localDoc) {
8906 return Promise.all(filenames.map(function (filename) {
8907 if (fileHasChanged(localDoc, doc, filename)) {
8908 return src.getAttachment(doc._id, filename);
8909 }
8910
8911 return target.getAttachment(localDoc._id, filename);
8912 }));
8913 }).catch(function (error) {
8914 /* istanbul ignore if */
8915 if (error.status !== 404) {
8916 throw error;
8917 }
8918
8919 return getDocAttachments(src, doc);
8920 });
8921}
8922
8923function createBulkGetOpts(diffs) {
8924 var requests = [];
8925 Object.keys(diffs).forEach(function (id) {
8926 var missingRevs = diffs[id].missing;
8927 missingRevs.forEach(function (missingRev) {
8928 requests.push({
8929 id: id,
8930 rev: missingRev
8931 });
8932 });
8933 });
8934
8935 return {
8936 docs: requests,
8937 revs: true,
8938 latest: true
8939 };
8940}
8941
8942//
8943// Fetch all the documents from the src as described in the "diffs",
8944// which is a mapping of docs IDs to revisions. If the state ever
8945// changes to "cancelled", then the returned promise will be rejected.
8946// Else it will be resolved with a list of fetched documents.
8947//
8948function getDocs(src, target, diffs, state) {
8949 diffs = clone(diffs); // we do not need to modify this
8950
8951 var resultDocs = [],
8952 ok = true;
8953
8954 function getAllDocs() {
8955
8956 var bulkGetOpts = createBulkGetOpts(diffs);
8957
8958 if (!bulkGetOpts.docs.length) { // optimization: skip empty requests
8959 return;
8960 }
8961
8962 return src.bulkGet(bulkGetOpts).then(function (bulkGetResponse) {
8963 /* istanbul ignore if */
8964 if (state.cancelled) {
8965 throw new Error('cancelled');
8966 }
8967 return Promise.all(bulkGetResponse.results.map(function (bulkGetInfo) {
8968 return Promise.all(bulkGetInfo.docs.map(function (doc) {
8969 var remoteDoc = doc.ok;
8970
8971 if (doc.error) {
8972 // when AUTO_COMPACTION is set, docs can be returned which look
8973 // like this: {"missing":"1-7c3ac256b693c462af8442f992b83696"}
8974 ok = false;
8975 }
8976
8977 if (!remoteDoc || !remoteDoc._attachments) {
8978 return remoteDoc;
8979 }
8980
8981 return getDocAttachmentsFromTargetOrSource(target, src, remoteDoc)
8982 .then(function (attachments) {
8983 var filenames = Object.keys(remoteDoc._attachments);
8984 attachments
8985 .forEach(function (attachment, i) {
8986 var att = remoteDoc._attachments[filenames[i]];
8987 delete att.stub;
8988 delete att.length;
8989 att.data = attachment;
8990 });
8991
8992 return remoteDoc;
8993 });
8994 }));
8995 }))
8996
8997 .then(function (results) {
8998 resultDocs = resultDocs.concat(flatten(results).filter(Boolean));
8999 });
9000 });
9001 }
9002
9003 function hasAttachments(doc) {
9004 return doc._attachments && Object.keys(doc._attachments).length > 0;
9005 }
9006
9007 function hasConflicts(doc) {
9008 return doc._conflicts && doc._conflicts.length > 0;
9009 }
9010
9011 function fetchRevisionOneDocs(ids) {
9012 // Optimization: fetch gen-1 docs and attachments in
9013 // a single request using _all_docs
9014 return src.allDocs({
9015 keys: ids,
9016 include_docs: true,
9017 conflicts: true
9018 }).then(function (res$$1) {
9019 if (state.cancelled) {
9020 throw new Error('cancelled');
9021 }
9022 res$$1.rows.forEach(function (row) {
9023 if (row.deleted || !row.doc || !isGenOne$1(row.value.rev) ||
9024 hasAttachments(row.doc) || hasConflicts(row.doc)) {
9025 // if any of these conditions apply, we need to fetch using get()
9026 return;
9027 }
9028
9029 // strip _conflicts array to appease CSG (#5793)
9030 /* istanbul ignore if */
9031 if (row.doc._conflicts) {
9032 delete row.doc._conflicts;
9033 }
9034
9035 // the doc we got back from allDocs() is sufficient
9036 resultDocs.push(row.doc);
9037 delete diffs[row.id];
9038 });
9039 });
9040 }
9041
9042 function getRevisionOneDocs() {
9043 // filter out the generation 1 docs and get them
9044 // leaving the non-generation one docs to be got otherwise
9045 var ids = Object.keys(diffs).filter(function (id) {
9046 var missing = diffs[id].missing;
9047 return missing.length === 1 && isGenOne$1(missing[0]);
9048 });
9049 if (ids.length > 0) {
9050 return fetchRevisionOneDocs(ids);
9051 }
9052 }
9053
9054 function returnResult() {
9055 return { ok:ok, docs:resultDocs };
9056 }
9057
9058 return Promise.resolve()
9059 .then(getRevisionOneDocs)
9060 .then(getAllDocs)
9061 .then(returnResult);
9062}
9063
9064var CHECKPOINT_VERSION = 1;
9065var REPLICATOR = "pouchdb";
9066// This is an arbitrary number to limit the
9067// amount of replication history we save in the checkpoint.
9068// If we save too much, the checkpoing docs will become very big,
9069// if we save fewer, we'll run a greater risk of having to
9070// read all the changes from 0 when checkpoint PUTs fail
9071// CouchDB 2.0 has a more involved history pruning,
9072// but let's go for the simple version for now.
9073var CHECKPOINT_HISTORY_SIZE = 5;
9074var LOWEST_SEQ = 0;
9075
9076function updateCheckpoint(db, id, checkpoint, session, returnValue) {
9077 return db.get(id).catch(function (err) {
9078 if (err.status === 404) {
9079 if (db.adapter === 'http' || db.adapter === 'https') ;
9080 return {
9081 session_id: session,
9082 _id: id,
9083 history: [],
9084 replicator: REPLICATOR,
9085 version: CHECKPOINT_VERSION
9086 };
9087 }
9088 throw err;
9089 }).then(function (doc) {
9090 if (returnValue.cancelled) {
9091 return;
9092 }
9093
9094 // if the checkpoint has not changed, do not update
9095 if (doc.last_seq === checkpoint) {
9096 return;
9097 }
9098
9099 // Filter out current entry for this replication
9100 doc.history = (doc.history || []).filter(function (item) {
9101 return item.session_id !== session;
9102 });
9103
9104 // Add the latest checkpoint to history
9105 doc.history.unshift({
9106 last_seq: checkpoint,
9107 session_id: session
9108 });
9109
9110 // Just take the last pieces in history, to
9111 // avoid really big checkpoint docs.
9112 // see comment on history size above
9113 doc.history = doc.history.slice(0, CHECKPOINT_HISTORY_SIZE);
9114
9115 doc.version = CHECKPOINT_VERSION;
9116 doc.replicator = REPLICATOR;
9117
9118 doc.session_id = session;
9119 doc.last_seq = checkpoint;
9120
9121 return db.put(doc).catch(function (err) {
9122 if (err.status === 409) {
9123 // retry; someone is trying to write a checkpoint simultaneously
9124 return updateCheckpoint(db, id, checkpoint, session, returnValue);
9125 }
9126 throw err;
9127 });
9128 });
9129}
9130
9131function Checkpointer(src, target, id, returnValue, opts) {
9132 this.src = src;
9133 this.target = target;
9134 this.id = id;
9135 this.returnValue = returnValue;
9136 this.opts = opts || {};
9137}
9138
9139Checkpointer.prototype.writeCheckpoint = function (checkpoint, session) {
9140 var self = this;
9141 return this.updateTarget(checkpoint, session).then(function () {
9142 return self.updateSource(checkpoint, session);
9143 });
9144};
9145
9146Checkpointer.prototype.updateTarget = function (checkpoint, session) {
9147 if (this.opts.writeTargetCheckpoint) {
9148 return updateCheckpoint(this.target, this.id, checkpoint,
9149 session, this.returnValue);
9150 } else {
9151 return Promise.resolve(true);
9152 }
9153};
9154
9155Checkpointer.prototype.updateSource = function (checkpoint, session) {
9156 if (this.opts.writeSourceCheckpoint) {
9157 var self = this;
9158 return updateCheckpoint(this.src, this.id, checkpoint,
9159 session, this.returnValue)
9160 .catch(function (err) {
9161 if (isForbiddenError(err)) {
9162 self.opts.writeSourceCheckpoint = false;
9163 return true;
9164 }
9165 throw err;
9166 });
9167 } else {
9168 return Promise.resolve(true);
9169 }
9170};
9171
9172var comparisons = {
9173 "undefined": function (targetDoc, sourceDoc) {
9174 // This is the previous comparison function
9175 if (collate(targetDoc.last_seq, sourceDoc.last_seq) === 0) {
9176 return sourceDoc.last_seq;
9177 }
9178 /* istanbul ignore next */
9179 return 0;
9180 },
9181 "1": function (targetDoc, sourceDoc) {
9182 // This is the comparison function ported from CouchDB
9183 return compareReplicationLogs(sourceDoc, targetDoc).last_seq;
9184 }
9185};
9186
9187Checkpointer.prototype.getCheckpoint = function () {
9188 var self = this;
9189
9190 if (self.opts && self.opts.writeSourceCheckpoint && !self.opts.writeTargetCheckpoint) {
9191 return self.src.get(self.id).then(function (sourceDoc) {
9192 return sourceDoc.last_seq || LOWEST_SEQ;
9193 }).catch(function (err) {
9194 /* istanbul ignore if */
9195 if (err.status !== 404) {
9196 throw err;
9197 }
9198 return LOWEST_SEQ;
9199 });
9200 }
9201
9202 return self.target.get(self.id).then(function (targetDoc) {
9203 if (self.opts && self.opts.writeTargetCheckpoint && !self.opts.writeSourceCheckpoint) {
9204 return targetDoc.last_seq || LOWEST_SEQ;
9205 }
9206
9207 return self.src.get(self.id).then(function (sourceDoc) {
9208 // Since we can't migrate an old version doc to a new one
9209 // (no session id), we just go with the lowest seq in this case
9210 /* istanbul ignore if */
9211 if (targetDoc.version !== sourceDoc.version) {
9212 return LOWEST_SEQ;
9213 }
9214
9215 var version;
9216 if (targetDoc.version) {
9217 version = targetDoc.version.toString();
9218 } else {
9219 version = "undefined";
9220 }
9221
9222 if (version in comparisons) {
9223 return comparisons[version](targetDoc, sourceDoc);
9224 }
9225 /* istanbul ignore next */
9226 return LOWEST_SEQ;
9227 }, function (err) {
9228 if (err.status === 404 && targetDoc.last_seq) {
9229 return self.src.put({
9230 _id: self.id,
9231 last_seq: LOWEST_SEQ
9232 }).then(function () {
9233 return LOWEST_SEQ;
9234 }, function (err) {
9235 if (isForbiddenError(err)) {
9236 self.opts.writeSourceCheckpoint = false;
9237 return targetDoc.last_seq;
9238 }
9239 /* istanbul ignore next */
9240 return LOWEST_SEQ;
9241 });
9242 }
9243 throw err;
9244 });
9245 }).catch(function (err) {
9246 if (err.status !== 404) {
9247 throw err;
9248 }
9249 return LOWEST_SEQ;
9250 });
9251};
9252// This checkpoint comparison is ported from CouchDBs source
9253// they come from here:
9254// https://github.com/apache/couchdb-couch-replicator/blob/master/src/couch_replicator.erl#L863-L906
9255
9256function compareReplicationLogs(srcDoc, tgtDoc) {
9257 if (srcDoc.session_id === tgtDoc.session_id) {
9258 return {
9259 last_seq: srcDoc.last_seq,
9260 history: srcDoc.history
9261 };
9262 }
9263
9264 return compareReplicationHistory(srcDoc.history, tgtDoc.history);
9265}
9266
9267function compareReplicationHistory(sourceHistory, targetHistory) {
9268 // the erlang loop via function arguments is not so easy to repeat in JS
9269 // therefore, doing this as recursion
9270 var S = sourceHistory[0];
9271 var sourceRest = sourceHistory.slice(1);
9272 var T = targetHistory[0];
9273 var targetRest = targetHistory.slice(1);
9274
9275 if (!S || targetHistory.length === 0) {
9276 return {
9277 last_seq: LOWEST_SEQ,
9278 history: []
9279 };
9280 }
9281
9282 var sourceId = S.session_id;
9283 /* istanbul ignore if */
9284 if (hasSessionId(sourceId, targetHistory)) {
9285 return {
9286 last_seq: S.last_seq,
9287 history: sourceHistory
9288 };
9289 }
9290
9291 var targetId = T.session_id;
9292 if (hasSessionId(targetId, sourceRest)) {
9293 return {
9294 last_seq: T.last_seq,
9295 history: targetRest
9296 };
9297 }
9298
9299 return compareReplicationHistory(sourceRest, targetRest);
9300}
9301
9302function hasSessionId(sessionId, history) {
9303 var props = history[0];
9304 var rest = history.slice(1);
9305
9306 if (!sessionId || history.length === 0) {
9307 return false;
9308 }
9309
9310 if (sessionId === props.session_id) {
9311 return true;
9312 }
9313
9314 return hasSessionId(sessionId, rest);
9315}
9316
9317function isForbiddenError(err) {
9318 return typeof err.status === 'number' && Math.floor(err.status / 100) === 4;
9319}
9320
9321var STARTING_BACK_OFF = 0;
9322
9323function backOff(opts, returnValue, error, callback) {
9324 if (opts.retry === false) {
9325 returnValue.emit('error', error);
9326 returnValue.removeAllListeners();
9327 return;
9328 }
9329 /* istanbul ignore if */
9330 if (typeof opts.back_off_function !== 'function') {
9331 opts.back_off_function = defaultBackOff;
9332 }
9333 returnValue.emit('requestError', error);
9334 if (returnValue.state === 'active' || returnValue.state === 'pending') {
9335 returnValue.emit('paused', error);
9336 returnValue.state = 'stopped';
9337 var backOffSet = function backoffTimeSet() {
9338 opts.current_back_off = STARTING_BACK_OFF;
9339 };
9340 var removeBackOffSetter = function removeBackOffTimeSet() {
9341 returnValue.removeListener('active', backOffSet);
9342 };
9343 returnValue.once('paused', removeBackOffSetter);
9344 returnValue.once('active', backOffSet);
9345 }
9346
9347 opts.current_back_off = opts.current_back_off || STARTING_BACK_OFF;
9348 opts.current_back_off = opts.back_off_function(opts.current_back_off);
9349 setTimeout(callback, opts.current_back_off);
9350}
9351
9352function sortObjectPropertiesByKey(queryParams) {
9353 return Object.keys(queryParams).sort(collate).reduce(function (result, key) {
9354 result[key] = queryParams[key];
9355 return result;
9356 }, {});
9357}
9358
9359// Generate a unique id particular to this replication.
9360// Not guaranteed to align perfectly with CouchDB's rep ids.
9361function generateReplicationId(src, target, opts) {
9362 var docIds = opts.doc_ids ? opts.doc_ids.sort(collate) : '';
9363 var filterFun = opts.filter ? opts.filter.toString() : '';
9364 var queryParams = '';
9365 var filterViewName = '';
9366 var selector = '';
9367
9368 // possibility for checkpoints to be lost here as behaviour of
9369 // JSON.stringify is not stable (see #6226)
9370 /* istanbul ignore if */
9371 if (opts.selector) {
9372 selector = JSON.stringify(opts.selector);
9373 }
9374
9375 if (opts.filter && opts.query_params) {
9376 queryParams = JSON.stringify(sortObjectPropertiesByKey(opts.query_params));
9377 }
9378
9379 if (opts.filter && opts.filter === '_view') {
9380 filterViewName = opts.view.toString();
9381 }
9382
9383 return Promise.all([src.id(), target.id()]).then(function (res) {
9384 var queryData = res[0] + res[1] + filterFun + filterViewName +
9385 queryParams + docIds + selector;
9386 return new Promise(function (resolve) {
9387 binaryMd5(queryData, resolve);
9388 });
9389 }).then(function (md5sum) {
9390 // can't use straight-up md5 alphabet, because
9391 // the char '/' is interpreted as being for attachments,
9392 // and + is also not url-safe
9393 md5sum = md5sum.replace(/\//g, '.').replace(/\+/g, '_');
9394 return '_local/' + md5sum;
9395 });
9396}
9397
9398function replicate(src, target, opts, returnValue, result) {
9399 var batches = []; // list of batches to be processed
9400 var currentBatch; // the batch currently being processed
9401 var pendingBatch = {
9402 seq: 0,
9403 changes: [],
9404 docs: []
9405 }; // next batch, not yet ready to be processed
9406 var writingCheckpoint = false; // true while checkpoint is being written
9407 var changesCompleted = false; // true when all changes received
9408 var replicationCompleted = false; // true when replication has completed
9409 var last_seq = 0;
9410 var continuous = opts.continuous || opts.live || false;
9411 var batch_size = opts.batch_size || 100;
9412 var batches_limit = opts.batches_limit || 10;
9413 var changesPending = false; // true while src.changes is running
9414 var doc_ids = opts.doc_ids;
9415 var selector = opts.selector;
9416 var repId;
9417 var checkpointer;
9418 var changedDocs = [];
9419 // Like couchdb, every replication gets a unique session id
9420 var session = uuid();
9421
9422 result = result || {
9423 ok: true,
9424 start_time: new Date().toISOString(),
9425 docs_read: 0,
9426 docs_written: 0,
9427 doc_write_failures: 0,
9428 errors: []
9429 };
9430
9431 var changesOpts = {};
9432 returnValue.ready(src, target);
9433
9434 function initCheckpointer() {
9435 if (checkpointer) {
9436 return Promise.resolve();
9437 }
9438 return generateReplicationId(src, target, opts).then(function (res$$1) {
9439 repId = res$$1;
9440
9441 var checkpointOpts = {};
9442 if (opts.checkpoint === false) {
9443 checkpointOpts = { writeSourceCheckpoint: false, writeTargetCheckpoint: false };
9444 } else if (opts.checkpoint === 'source') {
9445 checkpointOpts = { writeSourceCheckpoint: true, writeTargetCheckpoint: false };
9446 } else if (opts.checkpoint === 'target') {
9447 checkpointOpts = { writeSourceCheckpoint: false, writeTargetCheckpoint: true };
9448 } else {
9449 checkpointOpts = { writeSourceCheckpoint: true, writeTargetCheckpoint: true };
9450 }
9451
9452 checkpointer = new Checkpointer(src, target, repId, returnValue, checkpointOpts);
9453 });
9454 }
9455
9456 function writeDocs() {
9457 changedDocs = [];
9458
9459 if (currentBatch.docs.length === 0) {
9460 return;
9461 }
9462 var docs = currentBatch.docs;
9463 var bulkOpts = {timeout: opts.timeout};
9464 return target.bulkDocs({docs: docs, new_edits: false}, bulkOpts).then(function (res$$1) {
9465 /* istanbul ignore if */
9466 if (returnValue.cancelled) {
9467 completeReplication();
9468 throw new Error('cancelled');
9469 }
9470
9471 // `res` doesn't include full documents (which live in `docs`), so we create a map of
9472 // (id -> error), and check for errors while iterating over `docs`
9473 var errorsById = Object.create(null);
9474 res$$1.forEach(function (res$$1) {
9475 if (res$$1.error) {
9476 errorsById[res$$1.id] = res$$1;
9477 }
9478 });
9479
9480 var errorsNo = Object.keys(errorsById).length;
9481 result.doc_write_failures += errorsNo;
9482 result.docs_written += docs.length - errorsNo;
9483
9484 docs.forEach(function (doc) {
9485 var error = errorsById[doc._id];
9486 if (error) {
9487 result.errors.push(error);
9488 // Normalize error name. i.e. 'Unauthorized' -> 'unauthorized' (eg Sync Gateway)
9489 var errorName = (error.name || '').toLowerCase();
9490 if (errorName === 'unauthorized' || errorName === 'forbidden') {
9491 returnValue.emit('denied', clone(error));
9492 } else {
9493 throw error;
9494 }
9495 } else {
9496 changedDocs.push(doc);
9497 }
9498 });
9499
9500 }, function (err) {
9501 result.doc_write_failures += docs.length;
9502 throw err;
9503 });
9504 }
9505
9506 function finishBatch() {
9507 if (currentBatch.error) {
9508 throw new Error('There was a problem getting docs.');
9509 }
9510 result.last_seq = last_seq = currentBatch.seq;
9511 var outResult = clone(result);
9512 if (changedDocs.length) {
9513 outResult.docs = changedDocs;
9514 // Attach 'pending' property if server supports it (CouchDB 2.0+)
9515 /* istanbul ignore if */
9516 if (typeof currentBatch.pending === 'number') {
9517 outResult.pending = currentBatch.pending;
9518 delete currentBatch.pending;
9519 }
9520 returnValue.emit('change', outResult);
9521 }
9522 writingCheckpoint = true;
9523 return checkpointer.writeCheckpoint(currentBatch.seq,
9524 session).then(function () {
9525 writingCheckpoint = false;
9526 /* istanbul ignore if */
9527 if (returnValue.cancelled) {
9528 completeReplication();
9529 throw new Error('cancelled');
9530 }
9531 currentBatch = undefined;
9532 getChanges();
9533 }).catch(function (err) {
9534 onCheckpointError(err);
9535 throw err;
9536 });
9537 }
9538
9539 function getDiffs() {
9540 var diff = {};
9541 currentBatch.changes.forEach(function (change) {
9542 // Couchbase Sync Gateway emits these, but we can ignore them
9543 /* istanbul ignore if */
9544 if (change.id === "_user/") {
9545 return;
9546 }
9547 diff[change.id] = change.changes.map(function (x) {
9548 return x.rev;
9549 });
9550 });
9551 return target.revsDiff(diff).then(function (diffs) {
9552 /* istanbul ignore if */
9553 if (returnValue.cancelled) {
9554 completeReplication();
9555 throw new Error('cancelled');
9556 }
9557 // currentBatch.diffs elements are deleted as the documents are written
9558 currentBatch.diffs = diffs;
9559 });
9560 }
9561
9562 function getBatchDocs() {
9563 return getDocs(src, target, currentBatch.diffs, returnValue).then(function (got) {
9564 currentBatch.error = !got.ok;
9565 got.docs.forEach(function (doc) {
9566 delete currentBatch.diffs[doc._id];
9567 result.docs_read++;
9568 currentBatch.docs.push(doc);
9569 });
9570 });
9571 }
9572
9573 function startNextBatch() {
9574 if (returnValue.cancelled || currentBatch) {
9575 return;
9576 }
9577 if (batches.length === 0) {
9578 processPendingBatch(true);
9579 return;
9580 }
9581 currentBatch = batches.shift();
9582 getDiffs()
9583 .then(getBatchDocs)
9584 .then(writeDocs)
9585 .then(finishBatch)
9586 .then(startNextBatch)
9587 .catch(function (err) {
9588 abortReplication('batch processing terminated with error', err);
9589 });
9590 }
9591
9592
9593 function processPendingBatch(immediate) {
9594 if (pendingBatch.changes.length === 0) {
9595 if (batches.length === 0 && !currentBatch) {
9596 if ((continuous && changesOpts.live) || changesCompleted) {
9597 returnValue.state = 'pending';
9598 returnValue.emit('paused');
9599 }
9600 if (changesCompleted) {
9601 completeReplication();
9602 }
9603 }
9604 return;
9605 }
9606 if (
9607 immediate ||
9608 changesCompleted ||
9609 pendingBatch.changes.length >= batch_size
9610 ) {
9611 batches.push(pendingBatch);
9612 pendingBatch = {
9613 seq: 0,
9614 changes: [],
9615 docs: []
9616 };
9617 if (returnValue.state === 'pending' || returnValue.state === 'stopped') {
9618 returnValue.state = 'active';
9619 returnValue.emit('active');
9620 }
9621 startNextBatch();
9622 }
9623 }
9624
9625
9626 function abortReplication(reason, err) {
9627 if (replicationCompleted) {
9628 return;
9629 }
9630 if (!err.message) {
9631 err.message = reason;
9632 }
9633 result.ok = false;
9634 result.status = 'aborting';
9635 batches = [];
9636 pendingBatch = {
9637 seq: 0,
9638 changes: [],
9639 docs: []
9640 };
9641 completeReplication(err);
9642 }
9643
9644
9645 function completeReplication(fatalError) {
9646 if (replicationCompleted) {
9647 return;
9648 }
9649 /* istanbul ignore if */
9650 if (returnValue.cancelled) {
9651 result.status = 'cancelled';
9652 if (writingCheckpoint) {
9653 return;
9654 }
9655 }
9656 result.status = result.status || 'complete';
9657 result.end_time = new Date().toISOString();
9658 result.last_seq = last_seq;
9659 replicationCompleted = true;
9660
9661 if (fatalError) {
9662 // need to extend the error because Firefox considers ".result" read-only
9663 fatalError = createError(fatalError);
9664 fatalError.result = result;
9665
9666 // Normalize error name. i.e. 'Unauthorized' -> 'unauthorized' (eg Sync Gateway)
9667 var errorName = (fatalError.name || '').toLowerCase();
9668 if (errorName === 'unauthorized' || errorName === 'forbidden') {
9669 returnValue.emit('error', fatalError);
9670 returnValue.removeAllListeners();
9671 } else {
9672 backOff(opts, returnValue, fatalError, function () {
9673 replicate(src, target, opts, returnValue);
9674 });
9675 }
9676 } else {
9677 returnValue.emit('complete', result);
9678 returnValue.removeAllListeners();
9679 }
9680 }
9681
9682
9683 function onChange(change, pending, lastSeq) {
9684 /* istanbul ignore if */
9685 if (returnValue.cancelled) {
9686 return completeReplication();
9687 }
9688 // Attach 'pending' property if server supports it (CouchDB 2.0+)
9689 /* istanbul ignore if */
9690 if (typeof pending === 'number') {
9691 pendingBatch.pending = pending;
9692 }
9693
9694 var filter = filterChange(opts)(change);
9695 if (!filter) {
9696 return;
9697 }
9698 pendingBatch.seq = change.seq || lastSeq;
9699 pendingBatch.changes.push(change);
9700 nextTick(function () {
9701 processPendingBatch(batches.length === 0 && changesOpts.live);
9702 });
9703 }
9704
9705
9706 function onChangesComplete(changes) {
9707 changesPending = false;
9708 /* istanbul ignore if */
9709 if (returnValue.cancelled) {
9710 return completeReplication();
9711 }
9712
9713 // if no results were returned then we're done,
9714 // else fetch more
9715 if (changes.results.length > 0) {
9716 changesOpts.since = changes.results[changes.results.length - 1].seq;
9717 getChanges();
9718 processPendingBatch(true);
9719 } else {
9720
9721 var complete = function () {
9722 if (continuous) {
9723 changesOpts.live = true;
9724 getChanges();
9725 } else {
9726 changesCompleted = true;
9727 }
9728 processPendingBatch(true);
9729 };
9730
9731 // update the checkpoint so we start from the right seq next time
9732 if (!currentBatch && changes.results.length === 0) {
9733 writingCheckpoint = true;
9734 checkpointer.writeCheckpoint(changes.last_seq,
9735 session).then(function () {
9736 writingCheckpoint = false;
9737 result.last_seq = last_seq = changes.last_seq;
9738 complete();
9739 })
9740 .catch(onCheckpointError);
9741 } else {
9742 complete();
9743 }
9744 }
9745 }
9746
9747
9748 function onChangesError(err) {
9749 changesPending = false;
9750 /* istanbul ignore if */
9751 if (returnValue.cancelled) {
9752 return completeReplication();
9753 }
9754 abortReplication('changes rejected', err);
9755 }
9756
9757
9758 function getChanges() {
9759 if (!(
9760 !changesPending &&
9761 !changesCompleted &&
9762 batches.length < batches_limit
9763 )) {
9764 return;
9765 }
9766 changesPending = true;
9767 function abortChanges() {
9768 changes.cancel();
9769 }
9770 function removeListener() {
9771 returnValue.removeListener('cancel', abortChanges);
9772 }
9773
9774 if (returnValue._changes) { // remove old changes() and listeners
9775 returnValue.removeListener('cancel', returnValue._abortChanges);
9776 returnValue._changes.cancel();
9777 }
9778 returnValue.once('cancel', abortChanges);
9779
9780 var changes = src.changes(changesOpts)
9781 .on('change', onChange);
9782 changes.then(removeListener, removeListener);
9783 changes.then(onChangesComplete)
9784 .catch(onChangesError);
9785
9786 if (opts.retry) {
9787 // save for later so we can cancel if necessary
9788 returnValue._changes = changes;
9789 returnValue._abortChanges = abortChanges;
9790 }
9791 }
9792
9793
9794 function startChanges() {
9795 initCheckpointer().then(function () {
9796 /* istanbul ignore if */
9797 if (returnValue.cancelled) {
9798 completeReplication();
9799 return;
9800 }
9801 return checkpointer.getCheckpoint().then(function (checkpoint) {
9802 last_seq = checkpoint;
9803 changesOpts = {
9804 since: last_seq,
9805 limit: batch_size,
9806 batch_size: batch_size,
9807 style: 'all_docs',
9808 doc_ids: doc_ids,
9809 selector: selector,
9810 return_docs: true // required so we know when we're done
9811 };
9812 if (opts.filter) {
9813 if (typeof opts.filter !== 'string') {
9814 // required for the client-side filter in onChange
9815 changesOpts.include_docs = true;
9816 } else { // ddoc filter
9817 changesOpts.filter = opts.filter;
9818 }
9819 }
9820 if ('heartbeat' in opts) {
9821 changesOpts.heartbeat = opts.heartbeat;
9822 }
9823 if ('timeout' in opts) {
9824 changesOpts.timeout = opts.timeout;
9825 }
9826 if (opts.query_params) {
9827 changesOpts.query_params = opts.query_params;
9828 }
9829 if (opts.view) {
9830 changesOpts.view = opts.view;
9831 }
9832 getChanges();
9833 });
9834 }).catch(function (err) {
9835 abortReplication('getCheckpoint rejected with ', err);
9836 });
9837 }
9838
9839 /* istanbul ignore next */
9840 function onCheckpointError(err) {
9841 writingCheckpoint = false;
9842 abortReplication('writeCheckpoint completed with error', err);
9843 }
9844
9845 /* istanbul ignore if */
9846 if (returnValue.cancelled) { // cancelled immediately
9847 completeReplication();
9848 return;
9849 }
9850
9851 if (!returnValue._addedListeners) {
9852 returnValue.once('cancel', completeReplication);
9853
9854 if (typeof opts.complete === 'function') {
9855 returnValue.once('error', opts.complete);
9856 returnValue.once('complete', function (result) {
9857 opts.complete(null, result);
9858 });
9859 }
9860 returnValue._addedListeners = true;
9861 }
9862
9863 if (typeof opts.since === 'undefined') {
9864 startChanges();
9865 } else {
9866 initCheckpointer().then(function () {
9867 writingCheckpoint = true;
9868 return checkpointer.writeCheckpoint(opts.since, session);
9869 }).then(function () {
9870 writingCheckpoint = false;
9871 /* istanbul ignore if */
9872 if (returnValue.cancelled) {
9873 completeReplication();
9874 return;
9875 }
9876 last_seq = opts.since;
9877 startChanges();
9878 }).catch(onCheckpointError);
9879 }
9880}
9881
9882// We create a basic promise so the caller can cancel the replication possibly
9883// before we have actually started listening to changes etc
9884inherits(Replication, EventEmitter);
9885function Replication() {
9886 EventEmitter.call(this);
9887 this.cancelled = false;
9888 this.state = 'pending';
9889 var self = this;
9890 var promise = new Promise(function (fulfill, reject) {
9891 self.once('complete', fulfill);
9892 self.once('error', reject);
9893 });
9894 self.then = function (resolve, reject) {
9895 return promise.then(resolve, reject);
9896 };
9897 self.catch = function (reject) {
9898 return promise.catch(reject);
9899 };
9900 // As we allow error handling via "error" event as well,
9901 // put a stub in here so that rejecting never throws UnhandledError.
9902 self.catch(function () {});
9903}
9904
9905Replication.prototype.cancel = function () {
9906 this.cancelled = true;
9907 this.state = 'cancelled';
9908 this.emit('cancel');
9909};
9910
9911Replication.prototype.ready = function (src, target) {
9912 var self = this;
9913 if (self._readyCalled) {
9914 return;
9915 }
9916 self._readyCalled = true;
9917
9918 function onDestroy() {
9919 self.cancel();
9920 }
9921 src.once('destroyed', onDestroy);
9922 target.once('destroyed', onDestroy);
9923 function cleanup() {
9924 src.removeListener('destroyed', onDestroy);
9925 target.removeListener('destroyed', onDestroy);
9926 }
9927 self.once('complete', cleanup);
9928};
9929
9930function toPouch(db, opts) {
9931 var PouchConstructor = opts.PouchConstructor;
9932 if (typeof db === 'string') {
9933 return new PouchConstructor(db, opts);
9934 } else {
9935 return db;
9936 }
9937}
9938
9939function replicateWrapper(src, target, opts, callback) {
9940
9941 if (typeof opts === 'function') {
9942 callback = opts;
9943 opts = {};
9944 }
9945 if (typeof opts === 'undefined') {
9946 opts = {};
9947 }
9948
9949 if (opts.doc_ids && !Array.isArray(opts.doc_ids)) {
9950 throw createError(BAD_REQUEST,
9951 "`doc_ids` filter parameter is not a list.");
9952 }
9953
9954 opts.complete = callback;
9955 opts = clone(opts);
9956 opts.continuous = opts.continuous || opts.live;
9957 opts.retry = ('retry' in opts) ? opts.retry : false;
9958 /*jshint validthis:true */
9959 opts.PouchConstructor = opts.PouchConstructor || this;
9960 var replicateRet = new Replication(opts);
9961 var srcPouch = toPouch(src, opts);
9962 var targetPouch = toPouch(target, opts);
9963 replicate(srcPouch, targetPouch, opts, replicateRet);
9964 return replicateRet;
9965}
9966
9967inherits(Sync, EventEmitter);
9968function sync(src, target, opts, callback) {
9969 if (typeof opts === 'function') {
9970 callback = opts;
9971 opts = {};
9972 }
9973 if (typeof opts === 'undefined') {
9974 opts = {};
9975 }
9976 opts = clone(opts);
9977 /*jshint validthis:true */
9978 opts.PouchConstructor = opts.PouchConstructor || this;
9979 src = toPouch(src, opts);
9980 target = toPouch(target, opts);
9981 return new Sync(src, target, opts, callback);
9982}
9983
9984function Sync(src, target, opts, callback) {
9985 var self = this;
9986 this.canceled = false;
9987
9988 var optsPush = opts.push ? $inject_Object_assign({}, opts, opts.push) : opts;
9989 var optsPull = opts.pull ? $inject_Object_assign({}, opts, opts.pull) : opts;
9990
9991 this.push = replicateWrapper(src, target, optsPush);
9992 this.pull = replicateWrapper(target, src, optsPull);
9993
9994 this.pushPaused = true;
9995 this.pullPaused = true;
9996
9997 function pullChange(change) {
9998 self.emit('change', {
9999 direction: 'pull',
10000 change: change
10001 });
10002 }
10003 function pushChange(change) {
10004 self.emit('change', {
10005 direction: 'push',
10006 change: change
10007 });
10008 }
10009 function pushDenied(doc) {
10010 self.emit('denied', {
10011 direction: 'push',
10012 doc: doc
10013 });
10014 }
10015 function pullDenied(doc) {
10016 self.emit('denied', {
10017 direction: 'pull',
10018 doc: doc
10019 });
10020 }
10021 function pushPaused() {
10022 self.pushPaused = true;
10023 /* istanbul ignore if */
10024 if (self.pullPaused) {
10025 self.emit('paused');
10026 }
10027 }
10028 function pullPaused() {
10029 self.pullPaused = true;
10030 /* istanbul ignore if */
10031 if (self.pushPaused) {
10032 self.emit('paused');
10033 }
10034 }
10035 function pushActive() {
10036 self.pushPaused = false;
10037 /* istanbul ignore if */
10038 if (self.pullPaused) {
10039 self.emit('active', {
10040 direction: 'push'
10041 });
10042 }
10043 }
10044 function pullActive() {
10045 self.pullPaused = false;
10046 /* istanbul ignore if */
10047 if (self.pushPaused) {
10048 self.emit('active', {
10049 direction: 'pull'
10050 });
10051 }
10052 }
10053
10054 var removed = {};
10055
10056 function removeAll(type) { // type is 'push' or 'pull'
10057 return function (event, func) {
10058 var isChange = event === 'change' &&
10059 (func === pullChange || func === pushChange);
10060 var isDenied = event === 'denied' &&
10061 (func === pullDenied || func === pushDenied);
10062 var isPaused = event === 'paused' &&
10063 (func === pullPaused || func === pushPaused);
10064 var isActive = event === 'active' &&
10065 (func === pullActive || func === pushActive);
10066
10067 if (isChange || isDenied || isPaused || isActive) {
10068 if (!(event in removed)) {
10069 removed[event] = {};
10070 }
10071 removed[event][type] = true;
10072 if (Object.keys(removed[event]).length === 2) {
10073 // both push and pull have asked to be removed
10074 self.removeAllListeners(event);
10075 }
10076 }
10077 };
10078 }
10079
10080 if (opts.live) {
10081 this.push.on('complete', self.pull.cancel.bind(self.pull));
10082 this.pull.on('complete', self.push.cancel.bind(self.push));
10083 }
10084
10085 function addOneListener(ee, event, listener) {
10086 if (ee.listeners(event).indexOf(listener) == -1) {
10087 ee.on(event, listener);
10088 }
10089 }
10090
10091 this.on('newListener', function (event) {
10092 if (event === 'change') {
10093 addOneListener(self.pull, 'change', pullChange);
10094 addOneListener(self.push, 'change', pushChange);
10095 } else if (event === 'denied') {
10096 addOneListener(self.pull, 'denied', pullDenied);
10097 addOneListener(self.push, 'denied', pushDenied);
10098 } else if (event === 'active') {
10099 addOneListener(self.pull, 'active', pullActive);
10100 addOneListener(self.push, 'active', pushActive);
10101 } else if (event === 'paused') {
10102 addOneListener(self.pull, 'paused', pullPaused);
10103 addOneListener(self.push, 'paused', pushPaused);
10104 }
10105 });
10106
10107 this.on('removeListener', function (event) {
10108 if (event === 'change') {
10109 self.pull.removeListener('change', pullChange);
10110 self.push.removeListener('change', pushChange);
10111 } else if (event === 'denied') {
10112 self.pull.removeListener('denied', pullDenied);
10113 self.push.removeListener('denied', pushDenied);
10114 } else if (event === 'active') {
10115 self.pull.removeListener('active', pullActive);
10116 self.push.removeListener('active', pushActive);
10117 } else if (event === 'paused') {
10118 self.pull.removeListener('paused', pullPaused);
10119 self.push.removeListener('paused', pushPaused);
10120 }
10121 });
10122
10123 this.pull.on('removeListener', removeAll('pull'));
10124 this.push.on('removeListener', removeAll('push'));
10125
10126 var promise = Promise.all([
10127 this.push,
10128 this.pull
10129 ]).then(function (resp) {
10130 var out = {
10131 push: resp[0],
10132 pull: resp[1]
10133 };
10134 self.emit('complete', out);
10135 if (callback) {
10136 callback(null, out);
10137 }
10138 self.removeAllListeners();
10139 return out;
10140 }, function (err) {
10141 self.cancel();
10142 if (callback) {
10143 // if there's a callback, then the callback can receive
10144 // the error event
10145 callback(err);
10146 } else {
10147 // if there's no callback, then we're safe to emit an error
10148 // event, which would otherwise throw an unhandled error
10149 // due to 'error' being a special event in EventEmitters
10150 self.emit('error', err);
10151 }
10152 self.removeAllListeners();
10153 if (callback) {
10154 // no sense throwing if we're already emitting an 'error' event
10155 throw err;
10156 }
10157 });
10158
10159 this.then = function (success, err) {
10160 return promise.then(success, err);
10161 };
10162
10163 this.catch = function (err) {
10164 return promise.catch(err);
10165 };
10166}
10167
10168Sync.prototype.cancel = function () {
10169 if (!this.canceled) {
10170 this.canceled = true;
10171 this.push.cancel();
10172 this.pull.cancel();
10173 }
10174};
10175
10176function replication(PouchDB) {
10177 PouchDB.replicate = replicateWrapper;
10178 PouchDB.sync = sync;
10179
10180 Object.defineProperty(PouchDB.prototype, 'replicate', {
10181 get: function () {
10182 var self = this;
10183 if (typeof this.replicateMethods === 'undefined') {
10184 this.replicateMethods = {
10185 from: function (other, opts, callback) {
10186 return self.constructor.replicate(other, self, opts, callback);
10187 },
10188 to: function (other, opts, callback) {
10189 return self.constructor.replicate(self, other, opts, callback);
10190 }
10191 };
10192 }
10193 return this.replicateMethods;
10194 }
10195 });
10196
10197 PouchDB.prototype.sync = function (dbName, opts, callback) {
10198 return this.constructor.sync(this, dbName, opts, callback);
10199 };
10200}
10201
10202PouchDB.plugin(LevelPouch$1)
10203 .plugin(HttpPouch$1)
10204 .plugin(mapreduce)
10205 .plugin(replication);
10206
10207// Pull from src because pouchdb-node/pouchdb-browser themselves
10208
10209export default PouchDB;
10210
\No newline at end of file