UNPKG

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