UNPKG

324 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 immediate = _interopDefault(_dereq_(3));
1801var uuidV4 = _interopDefault(_dereq_(7));
1802var Md5 = _interopDefault(_dereq_(6));
1803var getArguments = _interopDefault(_dereq_(1));
1804var inherits = _interopDefault(_dereq_(4));
1805var events = _dereq_(2);
1806
1807function mangle(key) {
1808 return '$' + key;
1809}
1810function unmangle(key) {
1811 return key.substring(1);
1812}
1813function Map$1() {
1814 this._store = {};
1815}
1816Map$1.prototype.get = function (key) {
1817 var mangled = mangle(key);
1818 return this._store[mangled];
1819};
1820Map$1.prototype.set = function (key, value) {
1821 var mangled = mangle(key);
1822 this._store[mangled] = value;
1823 return true;
1824};
1825Map$1.prototype.has = function (key) {
1826 var mangled = mangle(key);
1827 return mangled in this._store;
1828};
1829Map$1.prototype["delete"] = function (key) {
1830 var mangled = mangle(key);
1831 var res = mangled in this._store;
1832 delete this._store[mangled];
1833 return res;
1834};
1835Map$1.prototype.forEach = function (cb) {
1836 var keys = Object.keys(this._store);
1837 for (var i = 0, len = keys.length; i < len; i++) {
1838 var key = keys[i];
1839 var value = this._store[key];
1840 key = unmangle(key);
1841 cb(value, key);
1842 }
1843};
1844Object.defineProperty(Map$1.prototype, 'size', {
1845 get: function () {
1846 return Object.keys(this._store).length;
1847 }
1848});
1849
1850function Set$1(array) {
1851 this._store = new Map$1();
1852
1853 // init with an array
1854 if (array && Array.isArray(array)) {
1855 for (var i = 0, len = array.length; i < len; i++) {
1856 this.add(array[i]);
1857 }
1858 }
1859}
1860Set$1.prototype.add = function (key) {
1861 return this._store.set(key, true);
1862};
1863Set$1.prototype.has = function (key) {
1864 return this._store.has(key);
1865};
1866Set$1.prototype.forEach = function (cb) {
1867 this._store.forEach(function (value, key) {
1868 cb(key);
1869 });
1870};
1871Object.defineProperty(Set$1.prototype, 'size', {
1872 get: function () {
1873 return this._store.size;
1874 }
1875});
1876
1877/* global Map,Set,Symbol */
1878// Based on https://kangax.github.io/compat-table/es6/ we can sniff out
1879// incomplete Map/Set implementations which would otherwise cause our tests to fail.
1880// Notably they fail in IE11 and iOS 8.4, which this prevents.
1881function supportsMapAndSet() {
1882 if (typeof Symbol === 'undefined' || typeof Map === 'undefined' || typeof Set === 'undefined') {
1883 return false;
1884 }
1885 var prop = Object.getOwnPropertyDescriptor(Map, Symbol.species);
1886 return prop && 'get' in prop && Map[Symbol.species] === Map;
1887}
1888
1889// based on https://github.com/montagejs/collections
1890
1891var ExportedSet;
1892var ExportedMap;
1893
1894{
1895 if (supportsMapAndSet()) { // prefer built-in Map/Set
1896 ExportedSet = Set;
1897 ExportedMap = Map;
1898 } else { // fall back to our polyfill
1899 ExportedSet = Set$1;
1900 ExportedMap = Map$1;
1901 }
1902}
1903
1904function isBinaryObject(object) {
1905 return (typeof ArrayBuffer !== 'undefined' && object instanceof ArrayBuffer) ||
1906 (typeof Blob !== 'undefined' && object instanceof Blob);
1907}
1908
1909function cloneArrayBuffer(buff) {
1910 if (typeof buff.slice === 'function') {
1911 return buff.slice(0);
1912 }
1913 // IE10-11 slice() polyfill
1914 var target = new ArrayBuffer(buff.byteLength);
1915 var targetArray = new Uint8Array(target);
1916 var sourceArray = new Uint8Array(buff);
1917 targetArray.set(sourceArray);
1918 return target;
1919}
1920
1921function cloneBinaryObject(object) {
1922 if (object instanceof ArrayBuffer) {
1923 return cloneArrayBuffer(object);
1924 }
1925 var size = object.size;
1926 var type = object.type;
1927 // Blob
1928 if (typeof object.slice === 'function') {
1929 return object.slice(0, size, type);
1930 }
1931 // PhantomJS slice() replacement
1932 return object.webkitSlice(0, size, type);
1933}
1934
1935// most of this is borrowed from lodash.isPlainObject:
1936// https://github.com/fis-components/lodash.isplainobject/
1937// blob/29c358140a74f252aeb08c9eb28bef86f2217d4a/index.js
1938
1939var funcToString = Function.prototype.toString;
1940var objectCtorString = funcToString.call(Object);
1941
1942function isPlainObject(value) {
1943 var proto = Object.getPrototypeOf(value);
1944 /* istanbul ignore if */
1945 if (proto === null) { // not sure when this happens, but I guess it can
1946 return true;
1947 }
1948 var Ctor = proto.constructor;
1949 return (typeof Ctor == 'function' &&
1950 Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
1951}
1952
1953function clone(object) {
1954 var newObject;
1955 var i;
1956 var len;
1957
1958 if (!object || typeof object !== 'object') {
1959 return object;
1960 }
1961
1962 if (Array.isArray(object)) {
1963 newObject = [];
1964 for (i = 0, len = object.length; i < len; i++) {
1965 newObject[i] = clone(object[i]);
1966 }
1967 return newObject;
1968 }
1969
1970 // special case: to avoid inconsistencies between IndexedDB
1971 // and other backends, we automatically stringify Dates
1972 if (object instanceof Date) {
1973 return object.toISOString();
1974 }
1975
1976 if (isBinaryObject(object)) {
1977 return cloneBinaryObject(object);
1978 }
1979
1980 if (!isPlainObject(object)) {
1981 return object; // don't clone objects like Workers
1982 }
1983
1984 newObject = {};
1985 for (i in object) {
1986 /* istanbul ignore else */
1987 if (Object.prototype.hasOwnProperty.call(object, i)) {
1988 var value = clone(object[i]);
1989 if (typeof value !== 'undefined') {
1990 newObject[i] = value;
1991 }
1992 }
1993 }
1994 return newObject;
1995}
1996
1997function once(fun) {
1998 var called = false;
1999 return getArguments(function (args) {
2000 /* istanbul ignore if */
2001 if (called) {
2002 // this is a smoke test and should never actually happen
2003 throw new Error('once called more than once');
2004 } else {
2005 called = true;
2006 fun.apply(this, args);
2007 }
2008 });
2009}
2010
2011function toPromise(func) {
2012 //create the function we will be returning
2013 return getArguments(function (args) {
2014 // Clone arguments
2015 args = clone(args);
2016 var self = this;
2017 // if the last argument is a function, assume its a callback
2018 var usedCB = (typeof args[args.length - 1] === 'function') ? args.pop() : false;
2019 var promise = new Promise(function (fulfill, reject) {
2020 var resp;
2021 try {
2022 var callback = once(function (err, mesg) {
2023 if (err) {
2024 reject(err);
2025 } else {
2026 fulfill(mesg);
2027 }
2028 });
2029 // create a callback for this invocation
2030 // apply the function in the orig context
2031 args.push(callback);
2032 resp = func.apply(self, args);
2033 if (resp && typeof resp.then === 'function') {
2034 fulfill(resp);
2035 }
2036 } catch (e) {
2037 reject(e);
2038 }
2039 });
2040 // if there is a callback, call it back
2041 if (usedCB) {
2042 promise.then(function (result) {
2043 usedCB(null, result);
2044 }, usedCB);
2045 }
2046 return promise;
2047 });
2048}
2049
2050function logApiCall(self, name, args) {
2051 /* istanbul ignore if */
2052 if (self.constructor.listeners('debug').length) {
2053 var logArgs = ['api', self.name, name];
2054 for (var i = 0; i < args.length - 1; i++) {
2055 logArgs.push(args[i]);
2056 }
2057 self.constructor.emit('debug', logArgs);
2058
2059 // override the callback itself to log the response
2060 var origCallback = args[args.length - 1];
2061 args[args.length - 1] = function (err, res) {
2062 var responseArgs = ['api', self.name, name];
2063 responseArgs = responseArgs.concat(
2064 err ? ['error', err] : ['success', res]
2065 );
2066 self.constructor.emit('debug', responseArgs);
2067 origCallback(err, res);
2068 };
2069 }
2070}
2071
2072function adapterFun(name, callback) {
2073 return toPromise(getArguments(function (args) {
2074 if (this._closed) {
2075 return Promise.reject(new Error('database is closed'));
2076 }
2077 if (this._destroyed) {
2078 return Promise.reject(new Error('database is destroyed'));
2079 }
2080 var self = this;
2081 logApiCall(self, name, args);
2082 if (!this.taskqueue.isReady) {
2083 return new Promise(function (fulfill, reject) {
2084 self.taskqueue.addTask(function (failed) {
2085 if (failed) {
2086 reject(failed);
2087 } else {
2088 fulfill(self[name].apply(self, args));
2089 }
2090 });
2091 });
2092 }
2093 return callback.apply(this, args);
2094 }));
2095}
2096
2097// like underscore/lodash _.pick()
2098function pick(obj, arr) {
2099 var res = {};
2100 for (var i = 0, len = arr.length; i < len; i++) {
2101 var prop = arr[i];
2102 if (prop in obj) {
2103 res[prop] = obj[prop];
2104 }
2105 }
2106 return res;
2107}
2108
2109// Most browsers throttle concurrent requests at 6, so it's silly
2110// to shim _bulk_get by trying to launch potentially hundreds of requests
2111// and then letting the majority time out. We can handle this ourselves.
2112var MAX_NUM_CONCURRENT_REQUESTS = 6;
2113
2114function identityFunction(x) {
2115 return x;
2116}
2117
2118function formatResultForOpenRevsGet(result) {
2119 return [{
2120 ok: result
2121 }];
2122}
2123
2124// shim for P/CouchDB adapters that don't directly implement _bulk_get
2125function bulkGet(db, opts, callback) {
2126 var requests = opts.docs;
2127
2128 // consolidate into one request per doc if possible
2129 var requestsById = new ExportedMap();
2130 requests.forEach(function (request) {
2131 if (requestsById.has(request.id)) {
2132 requestsById.get(request.id).push(request);
2133 } else {
2134 requestsById.set(request.id, [request]);
2135 }
2136 });
2137
2138 var numDocs = requestsById.size;
2139 var numDone = 0;
2140 var perDocResults = new Array(numDocs);
2141
2142 function collapseResultsAndFinish() {
2143 var results = [];
2144 perDocResults.forEach(function (res) {
2145 res.docs.forEach(function (info) {
2146 results.push({
2147 id: res.id,
2148 docs: [info]
2149 });
2150 });
2151 });
2152 callback(null, {results: results});
2153 }
2154
2155 function checkDone() {
2156 if (++numDone === numDocs) {
2157 collapseResultsAndFinish();
2158 }
2159 }
2160
2161 function gotResult(docIndex, id, docs) {
2162 perDocResults[docIndex] = {id: id, docs: docs};
2163 checkDone();
2164 }
2165
2166 var allRequests = [];
2167 requestsById.forEach(function (value, key) {
2168 allRequests.push(key);
2169 });
2170
2171 var i = 0;
2172
2173 function nextBatch() {
2174
2175 if (i >= allRequests.length) {
2176 return;
2177 }
2178
2179 var upTo = Math.min(i + MAX_NUM_CONCURRENT_REQUESTS, allRequests.length);
2180 var batch = allRequests.slice(i, upTo);
2181 processBatch(batch, i);
2182 i += batch.length;
2183 }
2184
2185 function processBatch(batch, offset) {
2186 batch.forEach(function (docId, j) {
2187 var docIdx = offset + j;
2188 var docRequests = requestsById.get(docId);
2189
2190 // just use the first request as the "template"
2191 // TODO: The _bulk_get API allows for more subtle use cases than this,
2192 // but for now it is unlikely that there will be a mix of different
2193 // "atts_since" or "attachments" in the same request, since it's just
2194 // replicate.js that is using this for the moment.
2195 // Also, atts_since is aspirational, since we don't support it yet.
2196 var docOpts = pick(docRequests[0], ['atts_since', 'attachments']);
2197 docOpts.open_revs = docRequests.map(function (request) {
2198 // rev is optional, open_revs disallowed
2199 return request.rev;
2200 });
2201
2202 // remove falsey / undefined revisions
2203 docOpts.open_revs = docOpts.open_revs.filter(identityFunction);
2204
2205 var formatResult = identityFunction;
2206
2207 if (docOpts.open_revs.length === 0) {
2208 delete docOpts.open_revs;
2209
2210 // when fetching only the "winning" leaf,
2211 // transform the result so it looks like an open_revs
2212 // request
2213 formatResult = formatResultForOpenRevsGet;
2214 }
2215
2216 // globally-supplied options
2217 ['revs', 'attachments', 'binary', 'ajax', 'latest'].forEach(function (param) {
2218 if (param in opts) {
2219 docOpts[param] = opts[param];
2220 }
2221 });
2222 db.get(docId, docOpts, function (err, res) {
2223 var result;
2224 /* istanbul ignore if */
2225 if (err) {
2226 result = [{error: err}];
2227 } else {
2228 result = formatResult(res);
2229 }
2230 gotResult(docIdx, docId, result);
2231 nextBatch();
2232 });
2233 });
2234 }
2235
2236 nextBatch();
2237
2238}
2239
2240var hasLocal;
2241
2242try {
2243 localStorage.setItem('_pouch_check_localstorage', 1);
2244 hasLocal = !!localStorage.getItem('_pouch_check_localstorage');
2245} catch (e) {
2246 hasLocal = false;
2247}
2248
2249function hasLocalStorage() {
2250 return hasLocal;
2251}
2252
2253// Custom nextTick() shim for browsers. In node, this will just be process.nextTick(). We
2254
2255inherits(Changes, events.EventEmitter);
2256
2257/* istanbul ignore next */
2258function attachBrowserEvents(self) {
2259 if (hasLocalStorage()) {
2260 addEventListener("storage", function (e) {
2261 self.emit(e.key);
2262 });
2263 }
2264}
2265
2266function Changes() {
2267 events.EventEmitter.call(this);
2268 this._listeners = {};
2269
2270 attachBrowserEvents(this);
2271}
2272Changes.prototype.addListener = function (dbName, id, db, opts) {
2273 /* istanbul ignore if */
2274 if (this._listeners[id]) {
2275 return;
2276 }
2277 var self = this;
2278 var inprogress = false;
2279 function eventFunction() {
2280 /* istanbul ignore if */
2281 if (!self._listeners[id]) {
2282 return;
2283 }
2284 if (inprogress) {
2285 inprogress = 'waiting';
2286 return;
2287 }
2288 inprogress = true;
2289 var changesOpts = pick(opts, [
2290 'style', 'include_docs', 'attachments', 'conflicts', 'filter',
2291 'doc_ids', 'view', 'since', 'query_params', 'binary', 'return_docs'
2292 ]);
2293
2294 /* istanbul ignore next */
2295 function onError() {
2296 inprogress = false;
2297 }
2298
2299 db.changes(changesOpts).on('change', function (c) {
2300 if (c.seq > opts.since && !opts.cancelled) {
2301 opts.since = c.seq;
2302 opts.onChange(c);
2303 }
2304 }).on('complete', function () {
2305 if (inprogress === 'waiting') {
2306 immediate(eventFunction);
2307 }
2308 inprogress = false;
2309 }).on('error', onError);
2310 }
2311 this._listeners[id] = eventFunction;
2312 this.on(dbName, eventFunction);
2313};
2314
2315Changes.prototype.removeListener = function (dbName, id) {
2316 /* istanbul ignore if */
2317 if (!(id in this._listeners)) {
2318 return;
2319 }
2320 events.EventEmitter.prototype.removeListener.call(this, dbName,
2321 this._listeners[id]);
2322 delete this._listeners[id];
2323};
2324
2325
2326/* istanbul ignore next */
2327Changes.prototype.notifyLocalWindows = function (dbName) {
2328 //do a useless change on a storage thing
2329 //in order to get other windows's listeners to activate
2330 if (hasLocalStorage()) {
2331 localStorage[dbName] = (localStorage[dbName] === "a") ? "b" : "a";
2332 }
2333};
2334
2335Changes.prototype.notify = function (dbName) {
2336 this.emit(dbName);
2337 this.notifyLocalWindows(dbName);
2338};
2339
2340function guardedConsole(method) {
2341 /* istanbul ignore else */
2342 if (typeof console !== 'undefined' && typeof console[method] === 'function') {
2343 var args = Array.prototype.slice.call(arguments, 1);
2344 console[method].apply(console, args);
2345 }
2346}
2347
2348function randomNumber(min, max) {
2349 var maxTimeout = 600000; // Hard-coded default of 10 minutes
2350 min = parseInt(min, 10) || 0;
2351 max = parseInt(max, 10);
2352 if (max !== max || max <= min) {
2353 max = (min || 1) << 1; //doubling
2354 } else {
2355 max = max + 1;
2356 }
2357 // In order to not exceed maxTimeout, pick a random value between half of maxTimeout and maxTimeout
2358 if (max > maxTimeout) {
2359 min = maxTimeout >> 1; // divide by two
2360 max = maxTimeout;
2361 }
2362 var ratio = Math.random();
2363 var range = max - min;
2364
2365 return ~~(range * ratio + min); // ~~ coerces to an int, but fast.
2366}
2367
2368function defaultBackOff(min) {
2369 var max = 0;
2370 if (!min) {
2371 max = 2000;
2372 }
2373 return randomNumber(min, max);
2374}
2375
2376// designed to give info to browser users, who are disturbed
2377// when they see http errors in the console
2378function explainError(status, str) {
2379 guardedConsole('info', 'The above ' + status + ' is totally normal. ' + str);
2380}
2381
2382var assign;
2383{
2384 if (typeof Object.assign === 'function') {
2385 assign = Object.assign;
2386 } else {
2387 // lite Object.assign polyfill based on
2388 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
2389 assign = function (target) {
2390 var to = Object(target);
2391
2392 for (var index = 1; index < arguments.length; index++) {
2393 var nextSource = arguments[index];
2394
2395 if (nextSource != null) { // Skip over if undefined or null
2396 for (var nextKey in nextSource) {
2397 // Avoid bugs when hasOwnProperty is shadowed
2398 if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
2399 to[nextKey] = nextSource[nextKey];
2400 }
2401 }
2402 }
2403 }
2404 return to;
2405 };
2406 }
2407}
2408
2409var $inject_Object_assign = assign;
2410
2411inherits(PouchError, Error);
2412
2413function PouchError(status, error, reason) {
2414 Error.call(this, reason);
2415 this.status = status;
2416 this.name = error;
2417 this.message = reason;
2418 this.error = true;
2419}
2420
2421PouchError.prototype.toString = function () {
2422 return JSON.stringify({
2423 status: this.status,
2424 name: this.name,
2425 message: this.message,
2426 reason: this.reason
2427 });
2428};
2429
2430var UNAUTHORIZED = new PouchError(401, 'unauthorized', "Name or password is incorrect.");
2431var MISSING_BULK_DOCS = new PouchError(400, 'bad_request', "Missing JSON list of 'docs'");
2432var MISSING_DOC = new PouchError(404, 'not_found', 'missing');
2433var REV_CONFLICT = new PouchError(409, 'conflict', 'Document update conflict');
2434var INVALID_ID = new PouchError(400, 'bad_request', '_id field must contain a string');
2435var MISSING_ID = new PouchError(412, 'missing_id', '_id is required for puts');
2436var RESERVED_ID = new PouchError(400, 'bad_request', 'Only reserved document ids may start with underscore.');
2437var NOT_OPEN = new PouchError(412, 'precondition_failed', 'Database not open');
2438var UNKNOWN_ERROR = new PouchError(500, 'unknown_error', 'Database encountered an unknown error');
2439var BAD_ARG = new PouchError(500, 'badarg', 'Some query argument is invalid');
2440var INVALID_REQUEST = new PouchError(400, 'invalid_request', 'Request was invalid');
2441var QUERY_PARSE_ERROR = new PouchError(400, 'query_parse_error', 'Some query parameter is invalid');
2442var DOC_VALIDATION = new PouchError(500, 'doc_validation', 'Bad special document member');
2443var BAD_REQUEST = new PouchError(400, 'bad_request', 'Something wrong with the request');
2444var NOT_AN_OBJECT = new PouchError(400, 'bad_request', 'Document must be a JSON object');
2445var DB_MISSING = new PouchError(404, 'not_found', 'Database not found');
2446var IDB_ERROR = new PouchError(500, 'indexed_db_went_bad', 'unknown');
2447var WSQ_ERROR = new PouchError(500, 'web_sql_went_bad', 'unknown');
2448var LDB_ERROR = new PouchError(500, 'levelDB_went_went_bad', 'unknown');
2449var FORBIDDEN = new PouchError(403, 'forbidden', 'Forbidden by design doc validate_doc_update function');
2450var INVALID_REV = new PouchError(400, 'bad_request', 'Invalid rev format');
2451var FILE_EXISTS = new PouchError(412, 'file_exists', 'The database could not be created, the file already exists.');
2452var MISSING_STUB = new PouchError(412, 'missing_stub', 'A pre-existing attachment stub wasn\'t found');
2453var INVALID_URL = new PouchError(413, 'invalid_url', 'Provided URL is invalid');
2454
2455function createError(error, reason) {
2456 function CustomPouchError(reason) {
2457 // inherit error properties from our parent error manually
2458 // so as to allow proper JSON parsing.
2459 /* jshint ignore:start */
2460 for (var p in error) {
2461 if (typeof error[p] !== 'function') {
2462 this[p] = error[p];
2463 }
2464 }
2465 /* jshint ignore:end */
2466 if (reason !== undefined) {
2467 this.reason = reason;
2468 }
2469 }
2470 CustomPouchError.prototype = PouchError.prototype;
2471 return new CustomPouchError(reason);
2472}
2473
2474function generateErrorFromResponse(err) {
2475
2476 if (typeof err !== 'object') {
2477 var data = err;
2478 err = UNKNOWN_ERROR;
2479 err.data = data;
2480 }
2481
2482 if ('error' in err && err.error === 'conflict') {
2483 err.name = 'conflict';
2484 err.status = 409;
2485 }
2486
2487 if (!('name' in err)) {
2488 err.name = err.error || 'unknown';
2489 }
2490
2491 if (!('status' in err)) {
2492 err.status = 500;
2493 }
2494
2495 if (!('message' in err)) {
2496 err.message = err.message || err.reason;
2497 }
2498
2499 return err;
2500}
2501
2502function tryFilter(filter, doc, req) {
2503 try {
2504 return !filter(doc, req);
2505 } catch (err) {
2506 var msg = 'Filter function threw: ' + err.toString();
2507 return createError(BAD_REQUEST, msg);
2508 }
2509}
2510
2511function filterChange(opts) {
2512 var req = {};
2513 var hasFilter = opts.filter && typeof opts.filter === 'function';
2514 req.query = opts.query_params;
2515
2516 return function filter(change) {
2517 if (!change.doc) {
2518 // CSG sends events on the changes feed that don't have documents,
2519 // this hack makes a whole lot of existing code robust.
2520 change.doc = {};
2521 }
2522
2523 var filterReturn = hasFilter && tryFilter(opts.filter, change.doc, req);
2524
2525 if (typeof filterReturn === 'object') {
2526 return filterReturn;
2527 }
2528
2529 if (filterReturn) {
2530 return false;
2531 }
2532
2533 if (!opts.include_docs) {
2534 delete change.doc;
2535 } else if (!opts.attachments) {
2536 for (var att in change.doc._attachments) {
2537 /* istanbul ignore else */
2538 if (change.doc._attachments.hasOwnProperty(att)) {
2539 change.doc._attachments[att].stub = true;
2540 }
2541 }
2542 }
2543 return true;
2544 };
2545}
2546
2547function flatten(arrs) {
2548 var res = [];
2549 for (var i = 0, len = arrs.length; i < len; i++) {
2550 res = res.concat(arrs[i]);
2551 }
2552 return res;
2553}
2554
2555// shim for Function.prototype.name,
2556
2557// Determine id an ID is valid
2558// - invalid IDs begin with an underescore that does not begin '_design' or
2559// '_local'
2560// - any other string value is a valid id
2561// Returns the specific error object for each case
2562function invalidIdError(id) {
2563 var err;
2564 if (!id) {
2565 err = createError(MISSING_ID);
2566 } else if (typeof id !== 'string') {
2567 err = createError(INVALID_ID);
2568 } else if (/^_/.test(id) && !(/^_(design|local)/).test(id)) {
2569 err = createError(RESERVED_ID);
2570 }
2571 if (err) {
2572 throw err;
2573 }
2574}
2575
2576// Checks if a PouchDB object is "remote" or not. This is
2577
2578function isRemote(db) {
2579 if (typeof db._remote === 'boolean') {
2580 return db._remote;
2581 }
2582 /* istanbul ignore next */
2583 if (typeof db.type === 'function') {
2584 guardedConsole('warn',
2585 'db.type() is deprecated and will be removed in ' +
2586 'a future version of PouchDB');
2587 return db.type() === 'http';
2588 }
2589 /* istanbul ignore next */
2590 return false;
2591}
2592
2593function listenerCount(ee, type) {
2594 return 'listenerCount' in ee ? ee.listenerCount(type) :
2595 events.EventEmitter.listenerCount(ee, type);
2596}
2597
2598function parseDesignDocFunctionName(s) {
2599 if (!s) {
2600 return null;
2601 }
2602 var parts = s.split('/');
2603 if (parts.length === 2) {
2604 return parts;
2605 }
2606 if (parts.length === 1) {
2607 return [s, s];
2608 }
2609 return null;
2610}
2611
2612function normalizeDesignDocFunctionName(s) {
2613 var normalized = parseDesignDocFunctionName(s);
2614 return normalized ? normalized.join('/') : null;
2615}
2616
2617// originally parseUri 1.2.2, now patched by us
2618// (c) Steven Levithan <stevenlevithan.com>
2619// MIT License
2620var keys = ["source", "protocol", "authority", "userInfo", "user", "password",
2621 "host", "port", "relative", "path", "directory", "file", "query", "anchor"];
2622var qName ="queryKey";
2623var qParser = /(?:^|&)([^&=]*)=?([^&]*)/g;
2624
2625// use the "loose" parser
2626/* eslint maxlen: 0, no-useless-escape: 0 */
2627var parser = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
2628
2629function parseUri(str) {
2630 var m = parser.exec(str);
2631 var uri = {};
2632 var i = 14;
2633
2634 while (i--) {
2635 var key = keys[i];
2636 var value = m[i] || "";
2637 var encoded = ['user', 'password'].indexOf(key) !== -1;
2638 uri[key] = encoded ? decodeURIComponent(value) : value;
2639 }
2640
2641 uri[qName] = {};
2642 uri[keys[12]].replace(qParser, function ($0, $1, $2) {
2643 if ($1) {
2644 uri[qName][$1] = $2;
2645 }
2646 });
2647
2648 return uri;
2649}
2650
2651// Based on https://github.com/alexdavid/scope-eval v0.0.3
2652// (source: https://unpkg.com/scope-eval@0.0.3/scope_eval.js)
2653// This is basically just a wrapper around new Function()
2654
2655function scopeEval(source, scope) {
2656 var keys = [];
2657 var values = [];
2658 for (var key in scope) {
2659 if (scope.hasOwnProperty(key)) {
2660 keys.push(key);
2661 values.push(scope[key]);
2662 }
2663 }
2664 keys.push(source);
2665 return Function.apply(null, keys).apply(null, values);
2666}
2667
2668// this is essentially the "update sugar" function from daleharvey/pouchdb#1388
2669// the diffFun tells us what delta to apply to the doc. it either returns
2670// the doc, or false if it doesn't need to do an update after all
2671function upsert(db, docId, diffFun) {
2672 return new Promise(function (fulfill, reject) {
2673 db.get(docId, function (err, doc) {
2674 if (err) {
2675 /* istanbul ignore next */
2676 if (err.status !== 404) {
2677 return reject(err);
2678 }
2679 doc = {};
2680 }
2681
2682 // the user might change the _rev, so save it for posterity
2683 var docRev = doc._rev;
2684 var newDoc = diffFun(doc);
2685
2686 if (!newDoc) {
2687 // if the diffFun returns falsy, we short-circuit as
2688 // an optimization
2689 return fulfill({updated: false, rev: docRev});
2690 }
2691
2692 // users aren't allowed to modify these values,
2693 // so reset them here
2694 newDoc._id = docId;
2695 newDoc._rev = docRev;
2696 fulfill(tryAndPut(db, newDoc, diffFun));
2697 });
2698 });
2699}
2700
2701function tryAndPut(db, doc, diffFun) {
2702 return db.put(doc).then(function (res) {
2703 return {
2704 updated: true,
2705 rev: res.rev
2706 };
2707 }, function (err) {
2708 /* istanbul ignore next */
2709 if (err.status !== 409) {
2710 throw err;
2711 }
2712 return upsert(db, doc._id, diffFun);
2713 });
2714}
2715
2716var thisAtob = function (str) {
2717 return atob(str);
2718};
2719
2720var thisBtoa = function (str) {
2721 return btoa(str);
2722};
2723
2724// Abstracts constructing a Blob object, so it also works in older
2725// browsers that don't support the native Blob constructor (e.g.
2726// old QtWebKit versions, Android < 4.4).
2727function createBlob(parts, properties) {
2728 /* global BlobBuilder,MSBlobBuilder,MozBlobBuilder,WebKitBlobBuilder */
2729 parts = parts || [];
2730 properties = properties || {};
2731 try {
2732 return new Blob(parts, properties);
2733 } catch (e) {
2734 if (e.name !== "TypeError") {
2735 throw e;
2736 }
2737 var Builder = typeof BlobBuilder !== 'undefined' ? BlobBuilder :
2738 typeof MSBlobBuilder !== 'undefined' ? MSBlobBuilder :
2739 typeof MozBlobBuilder !== 'undefined' ? MozBlobBuilder :
2740 WebKitBlobBuilder;
2741 var builder = new Builder();
2742 for (var i = 0; i < parts.length; i += 1) {
2743 builder.append(parts[i]);
2744 }
2745 return builder.getBlob(properties.type);
2746 }
2747}
2748
2749// From http://stackoverflow.com/questions/14967647/ (continues on next line)
2750// encode-decode-image-with-base64-breaks-image (2013-04-21)
2751function binaryStringToArrayBuffer(bin) {
2752 var length = bin.length;
2753 var buf = new ArrayBuffer(length);
2754 var arr = new Uint8Array(buf);
2755 for (var i = 0; i < length; i++) {
2756 arr[i] = bin.charCodeAt(i);
2757 }
2758 return buf;
2759}
2760
2761function binStringToBluffer(binString, type) {
2762 return createBlob([binaryStringToArrayBuffer(binString)], {type: type});
2763}
2764
2765function b64ToBluffer(b64, type) {
2766 return binStringToBluffer(thisAtob(b64), type);
2767}
2768
2769//Can't find original post, but this is close
2770//http://stackoverflow.com/questions/6965107/ (continues on next line)
2771//converting-between-strings-and-arraybuffers
2772function arrayBufferToBinaryString(buffer) {
2773 var binary = '';
2774 var bytes = new Uint8Array(buffer);
2775 var length = bytes.byteLength;
2776 for (var i = 0; i < length; i++) {
2777 binary += String.fromCharCode(bytes[i]);
2778 }
2779 return binary;
2780}
2781
2782// shim for browsers that don't support it
2783function readAsBinaryString(blob, callback) {
2784 var reader = new FileReader();
2785 var hasBinaryString = typeof reader.readAsBinaryString === 'function';
2786 reader.onloadend = function (e) {
2787 var result = e.target.result || '';
2788 if (hasBinaryString) {
2789 return callback(result);
2790 }
2791 callback(arrayBufferToBinaryString(result));
2792 };
2793 if (hasBinaryString) {
2794 reader.readAsBinaryString(blob);
2795 } else {
2796 reader.readAsArrayBuffer(blob);
2797 }
2798}
2799
2800function blobToBinaryString(blobOrBuffer, callback) {
2801 readAsBinaryString(blobOrBuffer, function (bin) {
2802 callback(bin);
2803 });
2804}
2805
2806function blobToBase64(blobOrBuffer, callback) {
2807 blobToBinaryString(blobOrBuffer, function (base64) {
2808 callback(thisBtoa(base64));
2809 });
2810}
2811
2812// simplified API. universal browser support is assumed
2813function readAsArrayBuffer(blob, callback) {
2814 var reader = new FileReader();
2815 reader.onloadend = function (e) {
2816 var result = e.target.result || new ArrayBuffer(0);
2817 callback(result);
2818 };
2819 reader.readAsArrayBuffer(blob);
2820}
2821
2822// this is not used in the browser
2823
2824var setImmediateShim = global.setImmediate || global.setTimeout;
2825var MD5_CHUNK_SIZE = 32768;
2826
2827function rawToBase64(raw) {
2828 return thisBtoa(raw);
2829}
2830
2831function sliceBlob(blob, start, end) {
2832 if (blob.webkitSlice) {
2833 return blob.webkitSlice(start, end);
2834 }
2835 return blob.slice(start, end);
2836}
2837
2838function appendBlob(buffer, blob, start, end, callback) {
2839 if (start > 0 || end < blob.size) {
2840 // only slice blob if we really need to
2841 blob = sliceBlob(blob, start, end);
2842 }
2843 readAsArrayBuffer(blob, function (arrayBuffer) {
2844 buffer.append(arrayBuffer);
2845 callback();
2846 });
2847}
2848
2849function appendString(buffer, string, start, end, callback) {
2850 if (start > 0 || end < string.length) {
2851 // only create a substring if we really need to
2852 string = string.substring(start, end);
2853 }
2854 buffer.appendBinary(string);
2855 callback();
2856}
2857
2858function binaryMd5(data, callback) {
2859 var inputIsString = typeof data === 'string';
2860 var len = inputIsString ? data.length : data.size;
2861 var chunkSize = Math.min(MD5_CHUNK_SIZE, len);
2862 var chunks = Math.ceil(len / chunkSize);
2863 var currentChunk = 0;
2864 var buffer = inputIsString ? new Md5() : new Md5.ArrayBuffer();
2865
2866 var append = inputIsString ? appendString : appendBlob;
2867
2868 function next() {
2869 setImmediateShim(loadNextChunk);
2870 }
2871
2872 function done() {
2873 var raw = buffer.end(true);
2874 var base64 = rawToBase64(raw);
2875 callback(base64);
2876 buffer.destroy();
2877 }
2878
2879 function loadNextChunk() {
2880 var start = currentChunk * chunkSize;
2881 var end = start + chunkSize;
2882 currentChunk++;
2883 if (currentChunk < chunks) {
2884 append(buffer, data, start, end, next);
2885 } else {
2886 append(buffer, data, start, end, done);
2887 }
2888 }
2889 loadNextChunk();
2890}
2891
2892function stringMd5(string) {
2893 return Md5.hash(string);
2894}
2895
2896function rev(doc, deterministic_revs) {
2897 var clonedDoc = clone(doc);
2898 if (!deterministic_revs) {
2899 return uuidV4.v4().replace(/-/g, '').toLowerCase();
2900 }
2901
2902 delete clonedDoc._rev_tree;
2903 return stringMd5(JSON.stringify(clonedDoc));
2904}
2905
2906var uuid = uuidV4.v4;
2907
2908// We fetch all leafs of the revision tree, and sort them based on tree length
2909// and whether they were deleted, undeleted documents with the longest revision
2910// tree (most edits) win
2911// The final sort algorithm is slightly documented in a sidebar here:
2912// http://guide.couchdb.org/draft/conflicts.html
2913function winningRev(metadata) {
2914 var winningId;
2915 var winningPos;
2916 var winningDeleted;
2917 var toVisit = metadata.rev_tree.slice();
2918 var node;
2919 while ((node = toVisit.pop())) {
2920 var tree = node.ids;
2921 var branches = tree[2];
2922 var pos = node.pos;
2923 if (branches.length) { // non-leaf
2924 for (var i = 0, len = branches.length; i < len; i++) {
2925 toVisit.push({pos: pos + 1, ids: branches[i]});
2926 }
2927 continue;
2928 }
2929 var deleted = !!tree[1].deleted;
2930 var id = tree[0];
2931 // sort by deleted, then pos, then id
2932 if (!winningId || (winningDeleted !== deleted ? winningDeleted :
2933 winningPos !== pos ? winningPos < pos : winningId < id)) {
2934 winningId = id;
2935 winningPos = pos;
2936 winningDeleted = deleted;
2937 }
2938 }
2939
2940 return winningPos + '-' + winningId;
2941}
2942
2943// Pretty much all below can be combined into a higher order function to
2944// traverse revisions
2945// The return value from the callback will be passed as context to all
2946// children of that node
2947function traverseRevTree(revs, callback) {
2948 var toVisit = revs.slice();
2949
2950 var node;
2951 while ((node = toVisit.pop())) {
2952 var pos = node.pos;
2953 var tree = node.ids;
2954 var branches = tree[2];
2955 var newCtx =
2956 callback(branches.length === 0, pos, tree[0], node.ctx, tree[1]);
2957 for (var i = 0, len = branches.length; i < len; i++) {
2958 toVisit.push({pos: pos + 1, ids: branches[i], ctx: newCtx});
2959 }
2960 }
2961}
2962
2963function sortByPos(a, b) {
2964 return a.pos - b.pos;
2965}
2966
2967function collectLeaves(revs) {
2968 var leaves = [];
2969 traverseRevTree(revs, function (isLeaf, pos, id, acc, opts) {
2970 if (isLeaf) {
2971 leaves.push({rev: pos + "-" + id, pos: pos, opts: opts});
2972 }
2973 });
2974 leaves.sort(sortByPos).reverse();
2975 for (var i = 0, len = leaves.length; i < len; i++) {
2976 delete leaves[i].pos;
2977 }
2978 return leaves;
2979}
2980
2981// returns revs of all conflicts that is leaves such that
2982// 1. are not deleted and
2983// 2. are different than winning revision
2984function collectConflicts(metadata) {
2985 var win = winningRev(metadata);
2986 var leaves = collectLeaves(metadata.rev_tree);
2987 var conflicts = [];
2988 for (var i = 0, len = leaves.length; i < len; i++) {
2989 var leaf = leaves[i];
2990 if (leaf.rev !== win && !leaf.opts.deleted) {
2991 conflicts.push(leaf.rev);
2992 }
2993 }
2994 return conflicts;
2995}
2996
2997// build up a list of all the paths to the leafs in this revision tree
2998function rootToLeaf(revs) {
2999 var paths = [];
3000 var toVisit = revs.slice();
3001 var node;
3002 while ((node = toVisit.pop())) {
3003 var pos = node.pos;
3004 var tree = node.ids;
3005 var id = tree[0];
3006 var opts = tree[1];
3007 var branches = tree[2];
3008 var isLeaf = branches.length === 0;
3009
3010 var history = node.history ? node.history.slice() : [];
3011 history.push({id: id, opts: opts});
3012 if (isLeaf) {
3013 paths.push({pos: (pos + 1 - history.length), ids: history});
3014 }
3015 for (var i = 0, len = branches.length; i < len; i++) {
3016 toVisit.push({pos: pos + 1, ids: branches[i], history: history});
3017 }
3018 }
3019 return paths.reverse();
3020}
3021
3022// for a better overview of what this is doing, read:
3023
3024function sortByPos$1(a, b) {
3025 return a.pos - b.pos;
3026}
3027
3028// classic binary search
3029function binarySearch(arr, item, comparator) {
3030 var low = 0;
3031 var high = arr.length;
3032 var mid;
3033 while (low < high) {
3034 mid = (low + high) >>> 1;
3035 if (comparator(arr[mid], item) < 0) {
3036 low = mid + 1;
3037 } else {
3038 high = mid;
3039 }
3040 }
3041 return low;
3042}
3043
3044// assuming the arr is sorted, insert the item in the proper place
3045function insertSorted(arr, item, comparator) {
3046 var idx = binarySearch(arr, item, comparator);
3047 arr.splice(idx, 0, item);
3048}
3049
3050// Turn a path as a flat array into a tree with a single branch.
3051// If any should be stemmed from the beginning of the array, that's passed
3052// in as the second argument
3053function pathToTree(path, numStemmed) {
3054 var root;
3055 var leaf;
3056 for (var i = numStemmed, len = path.length; i < len; i++) {
3057 var node = path[i];
3058 var currentLeaf = [node.id, node.opts, []];
3059 if (leaf) {
3060 leaf[2].push(currentLeaf);
3061 leaf = currentLeaf;
3062 } else {
3063 root = leaf = currentLeaf;
3064 }
3065 }
3066 return root;
3067}
3068
3069// compare the IDs of two trees
3070function compareTree(a, b) {
3071 return a[0] < b[0] ? -1 : 1;
3072}
3073
3074// Merge two trees together
3075// The roots of tree1 and tree2 must be the same revision
3076function mergeTree(in_tree1, in_tree2) {
3077 var queue = [{tree1: in_tree1, tree2: in_tree2}];
3078 var conflicts = false;
3079 while (queue.length > 0) {
3080 var item = queue.pop();
3081 var tree1 = item.tree1;
3082 var tree2 = item.tree2;
3083
3084 if (tree1[1].status || tree2[1].status) {
3085 tree1[1].status =
3086 (tree1[1].status === 'available' ||
3087 tree2[1].status === 'available') ? 'available' : 'missing';
3088 }
3089
3090 for (var i = 0; i < tree2[2].length; i++) {
3091 if (!tree1[2][0]) {
3092 conflicts = 'new_leaf';
3093 tree1[2][0] = tree2[2][i];
3094 continue;
3095 }
3096
3097 var merged = false;
3098 for (var j = 0; j < tree1[2].length; j++) {
3099 if (tree1[2][j][0] === tree2[2][i][0]) {
3100 queue.push({tree1: tree1[2][j], tree2: tree2[2][i]});
3101 merged = true;
3102 }
3103 }
3104 if (!merged) {
3105 conflicts = 'new_branch';
3106 insertSorted(tree1[2], tree2[2][i], compareTree);
3107 }
3108 }
3109 }
3110 return {conflicts: conflicts, tree: in_tree1};
3111}
3112
3113function doMerge(tree, path, dontExpand) {
3114 var restree = [];
3115 var conflicts = false;
3116 var merged = false;
3117 var res;
3118
3119 if (!tree.length) {
3120 return {tree: [path], conflicts: 'new_leaf'};
3121 }
3122
3123 for (var i = 0, len = tree.length; i < len; i++) {
3124 var branch = tree[i];
3125 if (branch.pos === path.pos && branch.ids[0] === path.ids[0]) {
3126 // Paths start at the same position and have the same root, so they need
3127 // merged
3128 res = mergeTree(branch.ids, path.ids);
3129 restree.push({pos: branch.pos, ids: res.tree});
3130 conflicts = conflicts || res.conflicts;
3131 merged = true;
3132 } else if (dontExpand !== true) {
3133 // The paths start at a different position, take the earliest path and
3134 // traverse up until it as at the same point from root as the path we
3135 // want to merge. If the keys match we return the longer path with the
3136 // other merged After stemming we dont want to expand the trees
3137
3138 var t1 = branch.pos < path.pos ? branch : path;
3139 var t2 = branch.pos < path.pos ? path : branch;
3140 var diff = t2.pos - t1.pos;
3141
3142 var candidateParents = [];
3143
3144 var trees = [];
3145 trees.push({ids: t1.ids, diff: diff, parent: null, parentIdx: null});
3146 while (trees.length > 0) {
3147 var item = trees.pop();
3148 if (item.diff === 0) {
3149 if (item.ids[0] === t2.ids[0]) {
3150 candidateParents.push(item);
3151 }
3152 continue;
3153 }
3154 var elements = item.ids[2];
3155 for (var j = 0, elementsLen = elements.length; j < elementsLen; j++) {
3156 trees.push({
3157 ids: elements[j],
3158 diff: item.diff - 1,
3159 parent: item.ids,
3160 parentIdx: j
3161 });
3162 }
3163 }
3164
3165 var el = candidateParents[0];
3166
3167 if (!el) {
3168 restree.push(branch);
3169 } else {
3170 res = mergeTree(el.ids, t2.ids);
3171 el.parent[2][el.parentIdx] = res.tree;
3172 restree.push({pos: t1.pos, ids: t1.ids});
3173 conflicts = conflicts || res.conflicts;
3174 merged = true;
3175 }
3176 } else {
3177 restree.push(branch);
3178 }
3179 }
3180
3181 // We didnt find
3182 if (!merged) {
3183 restree.push(path);
3184 }
3185
3186 restree.sort(sortByPos$1);
3187
3188 return {
3189 tree: restree,
3190 conflicts: conflicts || 'internal_node'
3191 };
3192}
3193
3194// To ensure we dont grow the revision tree infinitely, we stem old revisions
3195function stem(tree, depth) {
3196 // First we break out the tree into a complete list of root to leaf paths
3197 var paths = rootToLeaf(tree);
3198 var stemmedRevs;
3199
3200 var result;
3201 for (var i = 0, len = paths.length; i < len; i++) {
3202 // Then for each path, we cut off the start of the path based on the
3203 // `depth` to stem to, and generate a new set of flat trees
3204 var path = paths[i];
3205 var stemmed = path.ids;
3206 var node;
3207 if (stemmed.length > depth) {
3208 // only do the stemming work if we actually need to stem
3209 if (!stemmedRevs) {
3210 stemmedRevs = {}; // avoid allocating this object unnecessarily
3211 }
3212 var numStemmed = stemmed.length - depth;
3213 node = {
3214 pos: path.pos + numStemmed,
3215 ids: pathToTree(stemmed, numStemmed)
3216 };
3217
3218 for (var s = 0; s < numStemmed; s++) {
3219 var rev = (path.pos + s) + '-' + stemmed[s].id;
3220 stemmedRevs[rev] = true;
3221 }
3222 } else { // no need to actually stem
3223 node = {
3224 pos: path.pos,
3225 ids: pathToTree(stemmed, 0)
3226 };
3227 }
3228
3229 // Then we remerge all those flat trees together, ensuring that we dont
3230 // connect trees that would go beyond the depth limit
3231 if (result) {
3232 result = doMerge(result, node, true).tree;
3233 } else {
3234 result = [node];
3235 }
3236 }
3237
3238 // this is memory-heavy per Chrome profiler, avoid unless we actually stemmed
3239 if (stemmedRevs) {
3240 traverseRevTree(result, function (isLeaf, pos, revHash) {
3241 // some revisions may have been removed in a branch but not in another
3242 delete stemmedRevs[pos + '-' + revHash];
3243 });
3244 }
3245
3246 return {
3247 tree: result,
3248 revs: stemmedRevs ? Object.keys(stemmedRevs) : []
3249 };
3250}
3251
3252function merge(tree, path, depth) {
3253 var newTree = doMerge(tree, path);
3254 var stemmed = stem(newTree.tree, depth);
3255 return {
3256 tree: stemmed.tree,
3257 stemmedRevs: stemmed.revs,
3258 conflicts: newTree.conflicts
3259 };
3260}
3261
3262// return true if a rev exists in the rev tree, false otherwise
3263
3264function getTrees(node) {
3265 return node.ids;
3266}
3267
3268// check if a specific revision of a doc has been deleted
3269// - metadata: the metadata object from the doc store
3270// - rev: (optional) the revision to check. defaults to winning revision
3271function isDeleted(metadata, rev) {
3272 if (!rev) {
3273 rev = winningRev(metadata);
3274 }
3275 var id = rev.substring(rev.indexOf('-') + 1);
3276 var toVisit = metadata.rev_tree.map(getTrees);
3277
3278 var tree;
3279 while ((tree = toVisit.pop())) {
3280 if (tree[0] === id) {
3281 return !!tree[1].deleted;
3282 }
3283 toVisit = toVisit.concat(tree[2]);
3284 }
3285}
3286
3287function isLocalId(id) {
3288 return (/^_local/).test(id);
3289}
3290
3291// returns the current leaf node for a given revision
3292function latest(rev, metadata) {
3293 var toVisit = metadata.rev_tree.slice();
3294 var node;
3295 while ((node = toVisit.pop())) {
3296 var pos = node.pos;
3297 var tree = node.ids;
3298 var id = tree[0];
3299 var opts = tree[1];
3300 var branches = tree[2];
3301 var isLeaf = branches.length === 0;
3302
3303 var history = node.history ? node.history.slice() : [];
3304 history.push({id: id, pos: pos, opts: opts});
3305
3306 if (isLeaf) {
3307 for (var i = 0, len = history.length; i < len; i++) {
3308 var historyNode = history[i];
3309 var historyRev = historyNode.pos + '-' + historyNode.id;
3310
3311 if (historyRev === rev) {
3312 // return the rev of this leaf
3313 return pos + '-' + id;
3314 }
3315 }
3316 }
3317
3318 for (var j = 0, l = branches.length; j < l; j++) {
3319 toVisit.push({pos: pos + 1, ids: branches[j], history: history});
3320 }
3321 }
3322
3323 /* istanbul ignore next */
3324 throw new Error('Unable to resolve latest revision for id ' + metadata.id + ', rev ' + rev);
3325}
3326
3327inherits(Changes$1, events.EventEmitter);
3328
3329function tryCatchInChangeListener(self, change, pending, lastSeq) {
3330 // isolate try/catches to avoid V8 deoptimizations
3331 try {
3332 self.emit('change', change, pending, lastSeq);
3333 } catch (e) {
3334 guardedConsole('error', 'Error in .on("change", function):', e);
3335 }
3336}
3337
3338function Changes$1(db, opts, callback) {
3339 events.EventEmitter.call(this);
3340 var self = this;
3341 this.db = db;
3342 opts = opts ? clone(opts) : {};
3343 var complete = opts.complete = once(function (err, resp) {
3344 if (err) {
3345 if (listenerCount(self, 'error') > 0) {
3346 self.emit('error', err);
3347 }
3348 } else {
3349 self.emit('complete', resp);
3350 }
3351 self.removeAllListeners();
3352 db.removeListener('destroyed', onDestroy);
3353 });
3354 if (callback) {
3355 self.on('complete', function (resp) {
3356 callback(null, resp);
3357 });
3358 self.on('error', callback);
3359 }
3360 function onDestroy() {
3361 self.cancel();
3362 }
3363 db.once('destroyed', onDestroy);
3364
3365 opts.onChange = function (change, pending, lastSeq) {
3366 /* istanbul ignore if */
3367 if (self.isCancelled) {
3368 return;
3369 }
3370 tryCatchInChangeListener(self, change, pending, lastSeq);
3371 };
3372
3373 var promise = new Promise(function (fulfill, reject) {
3374 opts.complete = function (err, res) {
3375 if (err) {
3376 reject(err);
3377 } else {
3378 fulfill(res);
3379 }
3380 };
3381 });
3382 self.once('cancel', function () {
3383 db.removeListener('destroyed', onDestroy);
3384 opts.complete(null, {status: 'cancelled'});
3385 });
3386 this.then = promise.then.bind(promise);
3387 this['catch'] = promise['catch'].bind(promise);
3388 this.then(function (result) {
3389 complete(null, result);
3390 }, complete);
3391
3392
3393
3394 if (!db.taskqueue.isReady) {
3395 db.taskqueue.addTask(function (failed) {
3396 if (failed) {
3397 opts.complete(failed);
3398 } else if (self.isCancelled) {
3399 self.emit('cancel');
3400 } else {
3401 self.validateChanges(opts);
3402 }
3403 });
3404 } else {
3405 self.validateChanges(opts);
3406 }
3407}
3408Changes$1.prototype.cancel = function () {
3409 this.isCancelled = true;
3410 if (this.db.taskqueue.isReady) {
3411 this.emit('cancel');
3412 }
3413};
3414function processChange(doc, metadata, opts) {
3415 var changeList = [{rev: doc._rev}];
3416 if (opts.style === 'all_docs') {
3417 changeList = collectLeaves(metadata.rev_tree)
3418 .map(function (x) { return {rev: x.rev}; });
3419 }
3420 var change = {
3421 id: metadata.id,
3422 changes: changeList,
3423 doc: doc
3424 };
3425
3426 if (isDeleted(metadata, doc._rev)) {
3427 change.deleted = true;
3428 }
3429 if (opts.conflicts) {
3430 change.doc._conflicts = collectConflicts(metadata);
3431 if (!change.doc._conflicts.length) {
3432 delete change.doc._conflicts;
3433 }
3434 }
3435 return change;
3436}
3437
3438Changes$1.prototype.validateChanges = function (opts) {
3439 var callback = opts.complete;
3440 var self = this;
3441
3442 /* istanbul ignore else */
3443 if (PouchDB._changesFilterPlugin) {
3444 PouchDB._changesFilterPlugin.validate(opts, function (err) {
3445 if (err) {
3446 return callback(err);
3447 }
3448 self.doChanges(opts);
3449 });
3450 } else {
3451 self.doChanges(opts);
3452 }
3453};
3454
3455Changes$1.prototype.doChanges = function (opts) {
3456 var self = this;
3457 var callback = opts.complete;
3458
3459 opts = clone(opts);
3460 if ('live' in opts && !('continuous' in opts)) {
3461 opts.continuous = opts.live;
3462 }
3463 opts.processChange = processChange;
3464
3465 if (opts.since === 'latest') {
3466 opts.since = 'now';
3467 }
3468 if (!opts.since) {
3469 opts.since = 0;
3470 }
3471 if (opts.since === 'now') {
3472 this.db.info().then(function (info) {
3473 /* istanbul ignore if */
3474 if (self.isCancelled) {
3475 callback(null, {status: 'cancelled'});
3476 return;
3477 }
3478 opts.since = info.update_seq;
3479 self.doChanges(opts);
3480 }, callback);
3481 return;
3482 }
3483
3484 /* istanbul ignore else */
3485 if (PouchDB._changesFilterPlugin) {
3486 PouchDB._changesFilterPlugin.normalize(opts);
3487 if (PouchDB._changesFilterPlugin.shouldFilter(this, opts)) {
3488 return PouchDB._changesFilterPlugin.filter(this, opts);
3489 }
3490 } else {
3491 ['doc_ids', 'filter', 'selector', 'view'].forEach(function (key) {
3492 if (key in opts) {
3493 guardedConsole('warn',
3494 'The "' + key + '" option was passed in to changes/replicate, ' +
3495 'but pouchdb-changes-filter plugin is not installed, so it ' +
3496 'was ignored. Please install the plugin to enable filtering.'
3497 );
3498 }
3499 });
3500 }
3501
3502 if (!('descending' in opts)) {
3503 opts.descending = false;
3504 }
3505
3506 // 0 and 1 should return 1 document
3507 opts.limit = opts.limit === 0 ? 1 : opts.limit;
3508 opts.complete = callback;
3509 var newPromise = this.db._changes(opts);
3510 /* istanbul ignore else */
3511 if (newPromise && typeof newPromise.cancel === 'function') {
3512 var cancel = self.cancel;
3513 self.cancel = getArguments(function (args) {
3514 newPromise.cancel();
3515 cancel.apply(this, args);
3516 });
3517 }
3518};
3519
3520/*
3521 * A generic pouch adapter
3522 */
3523
3524function compare(left, right) {
3525 return left < right ? -1 : left > right ? 1 : 0;
3526}
3527
3528// Wrapper for functions that call the bulkdocs api with a single doc,
3529// if the first result is an error, return an error
3530function yankError(callback, docId) {
3531 return function (err, results) {
3532 if (err || (results[0] && results[0].error)) {
3533 err = err || results[0];
3534 err.docId = docId;
3535 callback(err);
3536 } else {
3537 callback(null, results.length ? results[0] : results);
3538 }
3539 };
3540}
3541
3542// clean docs given to us by the user
3543function cleanDocs(docs) {
3544 for (var i = 0; i < docs.length; i++) {
3545 var doc = docs[i];
3546 if (doc._deleted) {
3547 delete doc._attachments; // ignore atts for deleted docs
3548 } else if (doc._attachments) {
3549 // filter out extraneous keys from _attachments
3550 var atts = Object.keys(doc._attachments);
3551 for (var j = 0; j < atts.length; j++) {
3552 var att = atts[j];
3553 doc._attachments[att] = pick(doc._attachments[att],
3554 ['data', 'digest', 'content_type', 'length', 'revpos', 'stub']);
3555 }
3556 }
3557 }
3558}
3559
3560// compare two docs, first by _id then by _rev
3561function compareByIdThenRev(a, b) {
3562 var idCompare = compare(a._id, b._id);
3563 if (idCompare !== 0) {
3564 return idCompare;
3565 }
3566 var aStart = a._revisions ? a._revisions.start : 0;
3567 var bStart = b._revisions ? b._revisions.start : 0;
3568 return compare(aStart, bStart);
3569}
3570
3571// for every node in a revision tree computes its distance from the closest
3572// leaf
3573function computeHeight(revs) {
3574 var height = {};
3575 var edges = [];
3576 traverseRevTree(revs, function (isLeaf, pos, id, prnt) {
3577 var rev$$1 = pos + "-" + id;
3578 if (isLeaf) {
3579 height[rev$$1] = 0;
3580 }
3581 if (prnt !== undefined) {
3582 edges.push({from: prnt, to: rev$$1});
3583 }
3584 return rev$$1;
3585 });
3586
3587 edges.reverse();
3588 edges.forEach(function (edge) {
3589 if (height[edge.from] === undefined) {
3590 height[edge.from] = 1 + height[edge.to];
3591 } else {
3592 height[edge.from] = Math.min(height[edge.from], 1 + height[edge.to]);
3593 }
3594 });
3595 return height;
3596}
3597
3598function allDocsKeysParse(opts) {
3599 var keys = ('limit' in opts) ?
3600 opts.keys.slice(opts.skip, opts.limit + opts.skip) :
3601 (opts.skip > 0) ? opts.keys.slice(opts.skip) : opts.keys;
3602 opts.keys = keys;
3603 opts.skip = 0;
3604 delete opts.limit;
3605 if (opts.descending) {
3606 keys.reverse();
3607 opts.descending = false;
3608 }
3609}
3610
3611// all compaction is done in a queue, to avoid attaching
3612// too many listeners at once
3613function doNextCompaction(self) {
3614 var task = self._compactionQueue[0];
3615 var opts = task.opts;
3616 var callback = task.callback;
3617 self.get('_local/compaction')["catch"](function () {
3618 return false;
3619 }).then(function (doc) {
3620 if (doc && doc.last_seq) {
3621 opts.last_seq = doc.last_seq;
3622 }
3623 self._compact(opts, function (err, res) {
3624 /* istanbul ignore if */
3625 if (err) {
3626 callback(err);
3627 } else {
3628 callback(null, res);
3629 }
3630 immediate(function () {
3631 self._compactionQueue.shift();
3632 if (self._compactionQueue.length) {
3633 doNextCompaction(self);
3634 }
3635 });
3636 });
3637 });
3638}
3639
3640function attachmentNameError(name) {
3641 if (name.charAt(0) === '_') {
3642 return name + ' is not a valid attachment name, attachment ' +
3643 'names cannot start with \'_\'';
3644 }
3645 return false;
3646}
3647
3648inherits(AbstractPouchDB, events.EventEmitter);
3649
3650function AbstractPouchDB() {
3651 events.EventEmitter.call(this);
3652
3653 // re-bind prototyped methods
3654 for (var p in AbstractPouchDB.prototype) {
3655 if (typeof this[p] === 'function') {
3656 this[p] = this[p].bind(this);
3657 }
3658 }
3659}
3660
3661AbstractPouchDB.prototype.post =
3662 adapterFun('post', function (doc, opts, callback) {
3663 if (typeof opts === 'function') {
3664 callback = opts;
3665 opts = {};
3666 }
3667 if (typeof doc !== 'object' || Array.isArray(doc)) {
3668 return callback(createError(NOT_AN_OBJECT));
3669 }
3670 this.bulkDocs({docs: [doc]}, opts, yankError(callback, doc._id));
3671});
3672
3673AbstractPouchDB.prototype.put = adapterFun('put', function (doc, opts, cb) {
3674 if (typeof opts === 'function') {
3675 cb = opts;
3676 opts = {};
3677 }
3678 if (typeof doc !== 'object' || Array.isArray(doc)) {
3679 return cb(createError(NOT_AN_OBJECT));
3680 }
3681 invalidIdError(doc._id);
3682 if (isLocalId(doc._id) && typeof this._putLocal === 'function') {
3683 if (doc._deleted) {
3684 return this._removeLocal(doc, cb);
3685 } else {
3686 return this._putLocal(doc, cb);
3687 }
3688 }
3689 var self = this;
3690 if (opts.force && doc._rev) {
3691 transformForceOptionToNewEditsOption();
3692 putDoc(function (err) {
3693 var result = err ? null : {ok: true, id: doc._id, rev: doc._rev};
3694 cb(err, result);
3695 });
3696 } else {
3697 putDoc(cb);
3698 }
3699
3700 function transformForceOptionToNewEditsOption() {
3701 var parts = doc._rev.split('-');
3702 var oldRevId = parts[1];
3703 var oldRevNum = parseInt(parts[0], 10);
3704
3705 var newRevNum = oldRevNum + 1;
3706 var newRevId = rev();
3707
3708 doc._revisions = {
3709 start: newRevNum,
3710 ids: [newRevId, oldRevId]
3711 };
3712 doc._rev = newRevNum + '-' + newRevId;
3713 opts.new_edits = false;
3714 }
3715 function putDoc(next) {
3716 if (typeof self._put === 'function' && opts.new_edits !== false) {
3717 self._put(doc, opts, next);
3718 } else {
3719 self.bulkDocs({docs: [doc]}, opts, yankError(next, doc._id));
3720 }
3721 }
3722});
3723
3724AbstractPouchDB.prototype.putAttachment =
3725 adapterFun('putAttachment', function (docId, attachmentId, rev$$1,
3726 blob, type) {
3727 var api = this;
3728 if (typeof type === 'function') {
3729 type = blob;
3730 blob = rev$$1;
3731 rev$$1 = null;
3732 }
3733 // Lets fix in https://github.com/pouchdb/pouchdb/issues/3267
3734 /* istanbul ignore if */
3735 if (typeof type === 'undefined') {
3736 type = blob;
3737 blob = rev$$1;
3738 rev$$1 = null;
3739 }
3740 if (!type) {
3741 guardedConsole('warn', 'Attachment', attachmentId, 'on document', docId, 'is missing content_type');
3742 }
3743
3744 function createAttachment(doc) {
3745 var prevrevpos = '_rev' in doc ? parseInt(doc._rev, 10) : 0;
3746 doc._attachments = doc._attachments || {};
3747 doc._attachments[attachmentId] = {
3748 content_type: type,
3749 data: blob,
3750 revpos: ++prevrevpos
3751 };
3752 return api.put(doc);
3753 }
3754
3755 return api.get(docId).then(function (doc) {
3756 if (doc._rev !== rev$$1) {
3757 throw createError(REV_CONFLICT);
3758 }
3759
3760 return createAttachment(doc);
3761 }, function (err) {
3762 // create new doc
3763 /* istanbul ignore else */
3764 if (err.reason === MISSING_DOC.message) {
3765 return createAttachment({_id: docId});
3766 } else {
3767 throw err;
3768 }
3769 });
3770});
3771
3772AbstractPouchDB.prototype.removeAttachment =
3773 adapterFun('removeAttachment', function (docId, attachmentId, rev$$1,
3774 callback) {
3775 var self = this;
3776 self.get(docId, function (err, obj) {
3777 /* istanbul ignore if */
3778 if (err) {
3779 callback(err);
3780 return;
3781 }
3782 if (obj._rev !== rev$$1) {
3783 callback(createError(REV_CONFLICT));
3784 return;
3785 }
3786 /* istanbul ignore if */
3787 if (!obj._attachments) {
3788 return callback();
3789 }
3790 delete obj._attachments[attachmentId];
3791 if (Object.keys(obj._attachments).length === 0) {
3792 delete obj._attachments;
3793 }
3794 self.put(obj, callback);
3795 });
3796});
3797
3798AbstractPouchDB.prototype.remove =
3799 adapterFun('remove', function (docOrId, optsOrRev, opts, callback) {
3800 var doc;
3801 if (typeof optsOrRev === 'string') {
3802 // id, rev, opts, callback style
3803 doc = {
3804 _id: docOrId,
3805 _rev: optsOrRev
3806 };
3807 if (typeof opts === 'function') {
3808 callback = opts;
3809 opts = {};
3810 }
3811 } else {
3812 // doc, opts, callback style
3813 doc = docOrId;
3814 if (typeof optsOrRev === 'function') {
3815 callback = optsOrRev;
3816 opts = {};
3817 } else {
3818 callback = opts;
3819 opts = optsOrRev;
3820 }
3821 }
3822 opts = opts || {};
3823 opts.was_delete = true;
3824 var newDoc = {_id: doc._id, _rev: (doc._rev || opts.rev)};
3825 newDoc._deleted = true;
3826 if (isLocalId(newDoc._id) && typeof this._removeLocal === 'function') {
3827 return this._removeLocal(doc, callback);
3828 }
3829 this.bulkDocs({docs: [newDoc]}, opts, yankError(callback, newDoc._id));
3830});
3831
3832AbstractPouchDB.prototype.revsDiff =
3833 adapterFun('revsDiff', function (req, opts, callback) {
3834 if (typeof opts === 'function') {
3835 callback = opts;
3836 opts = {};
3837 }
3838 var ids = Object.keys(req);
3839
3840 if (!ids.length) {
3841 return callback(null, {});
3842 }
3843
3844 var count = 0;
3845 var missing = new ExportedMap();
3846
3847 function addToMissing(id, revId) {
3848 if (!missing.has(id)) {
3849 missing.set(id, {missing: []});
3850 }
3851 missing.get(id).missing.push(revId);
3852 }
3853
3854 function processDoc(id, rev_tree) {
3855 // Is this fast enough? Maybe we should switch to a set simulated by a map
3856 var missingForId = req[id].slice(0);
3857 traverseRevTree(rev_tree, function (isLeaf, pos, revHash, ctx,
3858 opts) {
3859 var rev$$1 = pos + '-' + revHash;
3860 var idx = missingForId.indexOf(rev$$1);
3861 if (idx === -1) {
3862 return;
3863 }
3864
3865 missingForId.splice(idx, 1);
3866 /* istanbul ignore if */
3867 if (opts.status !== 'available') {
3868 addToMissing(id, rev$$1);
3869 }
3870 });
3871
3872 // Traversing the tree is synchronous, so now `missingForId` contains
3873 // revisions that were not found in the tree
3874 missingForId.forEach(function (rev$$1) {
3875 addToMissing(id, rev$$1);
3876 });
3877 }
3878
3879 ids.map(function (id) {
3880 this._getRevisionTree(id, function (err, rev_tree) {
3881 if (err && err.status === 404 && err.message === 'missing') {
3882 missing.set(id, {missing: req[id]});
3883 } else if (err) {
3884 /* istanbul ignore next */
3885 return callback(err);
3886 } else {
3887 processDoc(id, rev_tree);
3888 }
3889
3890 if (++count === ids.length) {
3891 // convert LazyMap to object
3892 var missingObj = {};
3893 missing.forEach(function (value, key) {
3894 missingObj[key] = value;
3895 });
3896 return callback(null, missingObj);
3897 }
3898 });
3899 }, this);
3900});
3901
3902// _bulk_get API for faster replication, as described in
3903// https://github.com/apache/couchdb-chttpd/pull/33
3904// At the "abstract" level, it will just run multiple get()s in
3905// parallel, because this isn't much of a performance cost
3906// for local databases (except the cost of multiple transactions, which is
3907// small). The http adapter overrides this in order
3908// to do a more efficient single HTTP request.
3909AbstractPouchDB.prototype.bulkGet =
3910 adapterFun('bulkGet', function (opts, callback) {
3911 bulkGet(this, opts, callback);
3912});
3913
3914// compact one document and fire callback
3915// by compacting we mean removing all revisions which
3916// are further from the leaf in revision tree than max_height
3917AbstractPouchDB.prototype.compactDocument =
3918 adapterFun('compactDocument', function (docId, maxHeight, callback) {
3919 var self = this;
3920 this._getRevisionTree(docId, function (err, revTree) {
3921 /* istanbul ignore if */
3922 if (err) {
3923 return callback(err);
3924 }
3925 var height = computeHeight(revTree);
3926 var candidates = [];
3927 var revs = [];
3928 Object.keys(height).forEach(function (rev$$1) {
3929 if (height[rev$$1] > maxHeight) {
3930 candidates.push(rev$$1);
3931 }
3932 });
3933
3934 traverseRevTree(revTree, function (isLeaf, pos, revHash, ctx, opts) {
3935 var rev$$1 = pos + '-' + revHash;
3936 if (opts.status === 'available' && candidates.indexOf(rev$$1) !== -1) {
3937 revs.push(rev$$1);
3938 }
3939 });
3940 self._doCompaction(docId, revs, callback);
3941 });
3942});
3943
3944// compact the whole database using single document
3945// compaction
3946AbstractPouchDB.prototype.compact =
3947 adapterFun('compact', function (opts, callback) {
3948 if (typeof opts === 'function') {
3949 callback = opts;
3950 opts = {};
3951 }
3952
3953 var self = this;
3954 opts = opts || {};
3955
3956 self._compactionQueue = self._compactionQueue || [];
3957 self._compactionQueue.push({opts: opts, callback: callback});
3958 if (self._compactionQueue.length === 1) {
3959 doNextCompaction(self);
3960 }
3961});
3962AbstractPouchDB.prototype._compact = function (opts, callback) {
3963 var self = this;
3964 var changesOpts = {
3965 return_docs: false,
3966 last_seq: opts.last_seq || 0
3967 };
3968 var promises = [];
3969
3970 function onChange(row) {
3971 promises.push(self.compactDocument(row.id, 0));
3972 }
3973 function onComplete(resp) {
3974 var lastSeq = resp.last_seq;
3975 Promise.all(promises).then(function () {
3976 return upsert(self, '_local/compaction', function deltaFunc(doc) {
3977 if (!doc.last_seq || doc.last_seq < lastSeq) {
3978 doc.last_seq = lastSeq;
3979 return doc;
3980 }
3981 return false; // somebody else got here first, don't update
3982 });
3983 }).then(function () {
3984 callback(null, {ok: true});
3985 })["catch"](callback);
3986 }
3987 self.changes(changesOpts)
3988 .on('change', onChange)
3989 .on('complete', onComplete)
3990 .on('error', callback);
3991};
3992
3993/* Begin api wrappers. Specific functionality to storage belongs in the
3994 _[method] */
3995AbstractPouchDB.prototype.get = adapterFun('get', function (id, opts, cb) {
3996 if (typeof opts === 'function') {
3997 cb = opts;
3998 opts = {};
3999 }
4000 if (typeof id !== 'string') {
4001 return cb(createError(INVALID_ID));
4002 }
4003 if (isLocalId(id) && typeof this._getLocal === 'function') {
4004 return this._getLocal(id, cb);
4005 }
4006 var leaves = [], self = this;
4007
4008 function finishOpenRevs() {
4009 var result = [];
4010 var count = leaves.length;
4011 /* istanbul ignore if */
4012 if (!count) {
4013 return cb(null, result);
4014 }
4015
4016 // order with open_revs is unspecified
4017 leaves.forEach(function (leaf) {
4018 self.get(id, {
4019 rev: leaf,
4020 revs: opts.revs,
4021 latest: opts.latest,
4022 attachments: opts.attachments,
4023 binary: opts.binary
4024 }, function (err, doc) {
4025 if (!err) {
4026 // using latest=true can produce duplicates
4027 var existing;
4028 for (var i = 0, l = result.length; i < l; i++) {
4029 if (result[i].ok && result[i].ok._rev === doc._rev) {
4030 existing = true;
4031 break;
4032 }
4033 }
4034 if (!existing) {
4035 result.push({ok: doc});
4036 }
4037 } else {
4038 result.push({missing: leaf});
4039 }
4040 count--;
4041 if (!count) {
4042 cb(null, result);
4043 }
4044 });
4045 });
4046 }
4047
4048 if (opts.open_revs) {
4049 if (opts.open_revs === "all") {
4050 this._getRevisionTree(id, function (err, rev_tree) {
4051 /* istanbul ignore if */
4052 if (err) {
4053 return cb(err);
4054 }
4055 leaves = collectLeaves(rev_tree).map(function (leaf) {
4056 return leaf.rev;
4057 });
4058 finishOpenRevs();
4059 });
4060 } else {
4061 if (Array.isArray(opts.open_revs)) {
4062 leaves = opts.open_revs;
4063 for (var i = 0; i < leaves.length; i++) {
4064 var l = leaves[i];
4065 // looks like it's the only thing couchdb checks
4066 if (!(typeof (l) === "string" && /^\d+-/.test(l))) {
4067 return cb(createError(INVALID_REV));
4068 }
4069 }
4070 finishOpenRevs();
4071 } else {
4072 return cb(createError(UNKNOWN_ERROR, 'function_clause'));
4073 }
4074 }
4075 return; // open_revs does not like other options
4076 }
4077
4078 return this._get(id, opts, function (err, result) {
4079 if (err) {
4080 err.docId = id;
4081 return cb(err);
4082 }
4083
4084 var doc = result.doc;
4085 var metadata = result.metadata;
4086 var ctx = result.ctx;
4087
4088 if (opts.conflicts) {
4089 var conflicts = collectConflicts(metadata);
4090 if (conflicts.length) {
4091 doc._conflicts = conflicts;
4092 }
4093 }
4094
4095 if (isDeleted(metadata, doc._rev)) {
4096 doc._deleted = true;
4097 }
4098
4099 if (opts.revs || opts.revs_info) {
4100 var splittedRev = doc._rev.split('-');
4101 var revNo = parseInt(splittedRev[0], 10);
4102 var revHash = splittedRev[1];
4103
4104 var paths = rootToLeaf(metadata.rev_tree);
4105 var path = null;
4106
4107 for (var i = 0; i < paths.length; i++) {
4108 var currentPath = paths[i];
4109 var hashIndex = currentPath.ids.map(function (x) { return x.id; })
4110 .indexOf(revHash);
4111 var hashFoundAtRevPos = hashIndex === (revNo - 1);
4112
4113 if (hashFoundAtRevPos || (!path && hashIndex !== -1)) {
4114 path = currentPath;
4115 }
4116 }
4117
4118 /* istanbul ignore if */
4119 if (!path) {
4120 err = new Error('invalid rev tree');
4121 err.docId = id;
4122 return cb(err);
4123 }
4124
4125 var indexOfRev = path.ids.map(function (x) { return x.id; })
4126 .indexOf(doc._rev.split('-')[1]) + 1;
4127 var howMany = path.ids.length - indexOfRev;
4128 path.ids.splice(indexOfRev, howMany);
4129 path.ids.reverse();
4130
4131 if (opts.revs) {
4132 doc._revisions = {
4133 start: (path.pos + path.ids.length) - 1,
4134 ids: path.ids.map(function (rev$$1) {
4135 return rev$$1.id;
4136 })
4137 };
4138 }
4139 if (opts.revs_info) {
4140 var pos = path.pos + path.ids.length;
4141 doc._revs_info = path.ids.map(function (rev$$1) {
4142 pos--;
4143 return {
4144 rev: pos + '-' + rev$$1.id,
4145 status: rev$$1.opts.status
4146 };
4147 });
4148 }
4149 }
4150
4151 if (opts.attachments && doc._attachments) {
4152 var attachments = doc._attachments;
4153 var count = Object.keys(attachments).length;
4154 if (count === 0) {
4155 return cb(null, doc);
4156 }
4157 Object.keys(attachments).forEach(function (key) {
4158 this._getAttachment(doc._id, key, attachments[key], {
4159 // Previously the revision handling was done in adapter.js
4160 // getAttachment, however since idb-next doesnt we need to
4161 // pass the rev through
4162 rev: doc._rev,
4163 binary: opts.binary,
4164 ctx: ctx
4165 }, function (err, data) {
4166 var att = doc._attachments[key];
4167 att.data = data;
4168 delete att.stub;
4169 delete att.length;
4170 if (!--count) {
4171 cb(null, doc);
4172 }
4173 });
4174 }, self);
4175 } else {
4176 if (doc._attachments) {
4177 for (var key in doc._attachments) {
4178 /* istanbul ignore else */
4179 if (doc._attachments.hasOwnProperty(key)) {
4180 doc._attachments[key].stub = true;
4181 }
4182 }
4183 }
4184 cb(null, doc);
4185 }
4186 });
4187});
4188
4189// TODO: I dont like this, it forces an extra read for every
4190// attachment read and enforces a confusing api between
4191// adapter.js and the adapter implementation
4192AbstractPouchDB.prototype.getAttachment =
4193 adapterFun('getAttachment', function (docId, attachmentId, opts, callback) {
4194 var self = this;
4195 if (opts instanceof Function) {
4196 callback = opts;
4197 opts = {};
4198 }
4199 this._get(docId, opts, function (err, res) {
4200 if (err) {
4201 return callback(err);
4202 }
4203 if (res.doc._attachments && res.doc._attachments[attachmentId]) {
4204 opts.ctx = res.ctx;
4205 opts.binary = true;
4206 self._getAttachment(docId, attachmentId,
4207 res.doc._attachments[attachmentId], opts, callback);
4208 } else {
4209 return callback(createError(MISSING_DOC));
4210 }
4211 });
4212});
4213
4214AbstractPouchDB.prototype.allDocs =
4215 adapterFun('allDocs', function (opts, callback) {
4216 if (typeof opts === 'function') {
4217 callback = opts;
4218 opts = {};
4219 }
4220 opts.skip = typeof opts.skip !== 'undefined' ? opts.skip : 0;
4221 if (opts.start_key) {
4222 opts.startkey = opts.start_key;
4223 }
4224 if (opts.end_key) {
4225 opts.endkey = opts.end_key;
4226 }
4227 if ('keys' in opts) {
4228 if (!Array.isArray(opts.keys)) {
4229 return callback(new TypeError('options.keys must be an array'));
4230 }
4231 var incompatibleOpt =
4232 ['startkey', 'endkey', 'key'].filter(function (incompatibleOpt) {
4233 return incompatibleOpt in opts;
4234 })[0];
4235 if (incompatibleOpt) {
4236 callback(createError(QUERY_PARSE_ERROR,
4237 'Query parameter `' + incompatibleOpt +
4238 '` is not compatible with multi-get'
4239 ));
4240 return;
4241 }
4242 if (!isRemote(this)) {
4243 allDocsKeysParse(opts);
4244 if (opts.keys.length === 0) {
4245 return this._allDocs({limit: 0}, callback);
4246 }
4247 }
4248 }
4249
4250 return this._allDocs(opts, callback);
4251});
4252
4253AbstractPouchDB.prototype.changes = function (opts, callback) {
4254 if (typeof opts === 'function') {
4255 callback = opts;
4256 opts = {};
4257 }
4258
4259 opts = opts || {};
4260
4261 // By default set return_docs to false if the caller has opts.live = true,
4262 // this will prevent us from collecting the set of changes indefinitely
4263 // resulting in growing memory
4264 opts.return_docs = ('return_docs' in opts) ? opts.return_docs : !opts.live;
4265
4266 return new Changes$1(this, opts, callback);
4267};
4268
4269AbstractPouchDB.prototype.close = adapterFun('close', function (callback) {
4270 this._closed = true;
4271 this.emit('closed');
4272 return this._close(callback);
4273});
4274
4275AbstractPouchDB.prototype.info = adapterFun('info', function (callback) {
4276 var self = this;
4277 this._info(function (err, info) {
4278 if (err) {
4279 return callback(err);
4280 }
4281 // assume we know better than the adapter, unless it informs us
4282 info.db_name = info.db_name || self.name;
4283 info.auto_compaction = !!(self.auto_compaction && !isRemote(self));
4284 info.adapter = self.adapter;
4285 callback(null, info);
4286 });
4287});
4288
4289AbstractPouchDB.prototype.id = adapterFun('id', function (callback) {
4290 return this._id(callback);
4291});
4292
4293/* istanbul ignore next */
4294AbstractPouchDB.prototype.type = function () {
4295 return (typeof this._type === 'function') ? this._type() : this.adapter;
4296};
4297
4298AbstractPouchDB.prototype.bulkDocs =
4299 adapterFun('bulkDocs', function (req, opts, callback) {
4300 if (typeof opts === 'function') {
4301 callback = opts;
4302 opts = {};
4303 }
4304
4305 opts = opts || {};
4306
4307 if (Array.isArray(req)) {
4308 req = {
4309 docs: req
4310 };
4311 }
4312
4313 if (!req || !req.docs || !Array.isArray(req.docs)) {
4314 return callback(createError(MISSING_BULK_DOCS));
4315 }
4316
4317 for (var i = 0; i < req.docs.length; ++i) {
4318 if (typeof req.docs[i] !== 'object' || Array.isArray(req.docs[i])) {
4319 return callback(createError(NOT_AN_OBJECT));
4320 }
4321 }
4322
4323 var attachmentError;
4324 req.docs.forEach(function (doc) {
4325 if (doc._attachments) {
4326 Object.keys(doc._attachments).forEach(function (name) {
4327 attachmentError = attachmentError || attachmentNameError(name);
4328 if (!doc._attachments[name].content_type) {
4329 guardedConsole('warn', 'Attachment', name, 'on document', doc._id, 'is missing content_type');
4330 }
4331 });
4332 }
4333 });
4334
4335 if (attachmentError) {
4336 return callback(createError(BAD_REQUEST, attachmentError));
4337 }
4338
4339 if (!('new_edits' in opts)) {
4340 if ('new_edits' in req) {
4341 opts.new_edits = req.new_edits;
4342 } else {
4343 opts.new_edits = true;
4344 }
4345 }
4346
4347 var adapter = this;
4348 if (!opts.new_edits && !isRemote(adapter)) {
4349 // ensure revisions of the same doc are sorted, so that
4350 // the local adapter processes them correctly (#2935)
4351 req.docs.sort(compareByIdThenRev);
4352 }
4353
4354 cleanDocs(req.docs);
4355
4356 // in the case of conflicts, we want to return the _ids to the user
4357 // however, the underlying adapter may destroy the docs array, so
4358 // create a copy here
4359 var ids = req.docs.map(function (doc) {
4360 return doc._id;
4361 });
4362
4363 return this._bulkDocs(req, opts, function (err, res) {
4364 if (err) {
4365 return callback(err);
4366 }
4367 if (!opts.new_edits) {
4368 // this is what couch does when new_edits is false
4369 res = res.filter(function (x) {
4370 return x.error;
4371 });
4372 }
4373 // add ids for error/conflict responses (not required for CouchDB)
4374 if (!isRemote(adapter)) {
4375 for (var i = 0, l = res.length; i < l; i++) {
4376 res[i].id = res[i].id || ids[i];
4377 }
4378 }
4379
4380 callback(null, res);
4381 });
4382});
4383
4384AbstractPouchDB.prototype.registerDependentDatabase =
4385 adapterFun('registerDependentDatabase', function (dependentDb,
4386 callback) {
4387 var depDB = new this.constructor(dependentDb, this.__opts);
4388
4389 function diffFun(doc) {
4390 doc.dependentDbs = doc.dependentDbs || {};
4391 if (doc.dependentDbs[dependentDb]) {
4392 return false; // no update required
4393 }
4394 doc.dependentDbs[dependentDb] = true;
4395 return doc;
4396 }
4397 upsert(this, '_local/_pouch_dependentDbs', diffFun)
4398 .then(function () {
4399 callback(null, {db: depDB});
4400 })["catch"](callback);
4401});
4402
4403AbstractPouchDB.prototype.destroy =
4404 adapterFun('destroy', function (opts, callback) {
4405
4406 if (typeof opts === 'function') {
4407 callback = opts;
4408 opts = {};
4409 }
4410
4411 var self = this;
4412 var usePrefix = 'use_prefix' in self ? self.use_prefix : true;
4413
4414 function destroyDb() {
4415 // call destroy method of the particular adaptor
4416 self._destroy(opts, function (err, resp) {
4417 if (err) {
4418 return callback(err);
4419 }
4420 self._destroyed = true;
4421 self.emit('destroyed');
4422 callback(null, resp || { 'ok': true });
4423 });
4424 }
4425
4426 if (isRemote(self)) {
4427 // no need to check for dependent DBs if it's a remote DB
4428 return destroyDb();
4429 }
4430
4431 self.get('_local/_pouch_dependentDbs', function (err, localDoc) {
4432 if (err) {
4433 /* istanbul ignore if */
4434 if (err.status !== 404) {
4435 return callback(err);
4436 } else { // no dependencies
4437 return destroyDb();
4438 }
4439 }
4440 var dependentDbs = localDoc.dependentDbs;
4441 var PouchDB = self.constructor;
4442 var deletedMap = Object.keys(dependentDbs).map(function (name) {
4443 // use_prefix is only false in the browser
4444 /* istanbul ignore next */
4445 var trueName = usePrefix ?
4446 name.replace(new RegExp('^' + PouchDB.prefix), '') : name;
4447 return new PouchDB(trueName, self.__opts).destroy();
4448 });
4449 Promise.all(deletedMap).then(destroyDb, callback);
4450 });
4451});
4452
4453function TaskQueue() {
4454 this.isReady = false;
4455 this.failed = false;
4456 this.queue = [];
4457}
4458
4459TaskQueue.prototype.execute = function () {
4460 var fun;
4461 if (this.failed) {
4462 while ((fun = this.queue.shift())) {
4463 fun(this.failed);
4464 }
4465 } else {
4466 while ((fun = this.queue.shift())) {
4467 fun();
4468 }
4469 }
4470};
4471
4472TaskQueue.prototype.fail = function (err) {
4473 this.failed = err;
4474 this.execute();
4475};
4476
4477TaskQueue.prototype.ready = function (db) {
4478 this.isReady = true;
4479 this.db = db;
4480 this.execute();
4481};
4482
4483TaskQueue.prototype.addTask = function (fun) {
4484 this.queue.push(fun);
4485 if (this.failed) {
4486 this.execute();
4487 }
4488};
4489
4490function parseAdapter(name, opts) {
4491 var match = name.match(/([a-z-]*):\/\/(.*)/);
4492 if (match) {
4493 // the http adapter expects the fully qualified name
4494 return {
4495 name: /https?/.test(match[1]) ? match[1] + '://' + match[2] : match[2],
4496 adapter: match[1]
4497 };
4498 }
4499
4500 var adapters = PouchDB.adapters;
4501 var preferredAdapters = PouchDB.preferredAdapters;
4502 var prefix = PouchDB.prefix;
4503 var adapterName = opts.adapter;
4504
4505 if (!adapterName) { // automatically determine adapter
4506 for (var i = 0; i < preferredAdapters.length; ++i) {
4507 adapterName = preferredAdapters[i];
4508 // check for browsers that have been upgraded from websql-only to websql+idb
4509 /* istanbul ignore if */
4510 if (adapterName === 'idb' && 'websql' in adapters &&
4511 hasLocalStorage() && localStorage['_pouch__websqldb_' + prefix + name]) {
4512 // log it, because this can be confusing during development
4513 guardedConsole('log', 'PouchDB is downgrading "' + name + '" to WebSQL to' +
4514 ' avoid data loss, because it was already opened with WebSQL.');
4515 continue; // keep using websql to avoid user data loss
4516 }
4517 break;
4518 }
4519 }
4520
4521 var adapter = adapters[adapterName];
4522
4523 // if adapter is invalid, then an error will be thrown later
4524 var usePrefix = (adapter && 'use_prefix' in adapter) ?
4525 adapter.use_prefix : true;
4526
4527 return {
4528 name: usePrefix ? (prefix + name) : name,
4529 adapter: adapterName
4530 };
4531}
4532
4533// OK, so here's the deal. Consider this code:
4534// var db1 = new PouchDB('foo');
4535// var db2 = new PouchDB('foo');
4536// db1.destroy();
4537// ^ these two both need to emit 'destroyed' events,
4538// as well as the PouchDB constructor itself.
4539// So we have one db object (whichever one got destroy() called on it)
4540// responsible for emitting the initial event, which then gets emitted
4541// by the constructor, which then broadcasts it to any other dbs
4542// that may have been created with the same name.
4543function prepareForDestruction(self) {
4544
4545 function onDestroyed(from_constructor) {
4546 self.removeListener('closed', onClosed);
4547 if (!from_constructor) {
4548 self.constructor.emit('destroyed', self.name);
4549 }
4550 }
4551
4552 function onClosed() {
4553 self.removeListener('destroyed', onDestroyed);
4554 self.constructor.emit('unref', self);
4555 }
4556
4557 self.once('destroyed', onDestroyed);
4558 self.once('closed', onClosed);
4559 self.constructor.emit('ref', self);
4560}
4561
4562inherits(PouchDB, AbstractPouchDB);
4563function PouchDB(name, opts) {
4564 // In Node our test suite only tests this for PouchAlt unfortunately
4565 /* istanbul ignore if */
4566 if (!(this instanceof PouchDB)) {
4567 return new PouchDB(name, opts);
4568 }
4569
4570 var self = this;
4571 opts = opts || {};
4572
4573 if (name && typeof name === 'object') {
4574 opts = name;
4575 name = opts.name;
4576 delete opts.name;
4577 }
4578
4579 if (opts.deterministic_revs === undefined) {
4580 opts.deterministic_revs = true;
4581 }
4582
4583 this.__opts = opts = clone(opts);
4584
4585 self.auto_compaction = opts.auto_compaction;
4586 self.prefix = PouchDB.prefix;
4587
4588 if (typeof name !== 'string') {
4589 throw new Error('Missing/invalid DB name');
4590 }
4591
4592 var prefixedName = (opts.prefix || '') + name;
4593 var backend = parseAdapter(prefixedName, opts);
4594
4595 opts.name = backend.name;
4596 opts.adapter = opts.adapter || backend.adapter;
4597
4598 self.name = name;
4599 self._adapter = opts.adapter;
4600 PouchDB.emit('debug', ['adapter', 'Picked adapter: ', opts.adapter]);
4601
4602 if (!PouchDB.adapters[opts.adapter] ||
4603 !PouchDB.adapters[opts.adapter].valid()) {
4604 throw new Error('Invalid Adapter: ' + opts.adapter);
4605 }
4606
4607 AbstractPouchDB.call(self);
4608 self.taskqueue = new TaskQueue();
4609
4610 self.adapter = opts.adapter;
4611
4612 PouchDB.adapters[opts.adapter].call(self, opts, function (err) {
4613 if (err) {
4614 return self.taskqueue.fail(err);
4615 }
4616 prepareForDestruction(self);
4617
4618 self.emit('created', self);
4619 PouchDB.emit('created', self.name);
4620 self.taskqueue.ready(self);
4621 });
4622
4623}
4624
4625// AbortController was introduced quite a while after fetch and
4626// isnt required for PouchDB to function so polyfill if needed
4627var a = (typeof AbortController !== 'undefined')
4628 ? AbortController
4629 : function () { return {abort: function () {}}; };
4630
4631var f$1 = fetch;
4632var h = Headers;
4633
4634PouchDB.adapters = {};
4635PouchDB.preferredAdapters = [];
4636
4637PouchDB.prefix = '_pouch_';
4638
4639var eventEmitter = new events.EventEmitter();
4640
4641function setUpEventEmitter(Pouch) {
4642 Object.keys(events.EventEmitter.prototype).forEach(function (key) {
4643 if (typeof events.EventEmitter.prototype[key] === 'function') {
4644 Pouch[key] = eventEmitter[key].bind(eventEmitter);
4645 }
4646 });
4647
4648 // these are created in constructor.js, and allow us to notify each DB with
4649 // the same name that it was destroyed, via the constructor object
4650 var destructListeners = Pouch._destructionListeners = new ExportedMap();
4651
4652 Pouch.on('ref', function onConstructorRef(db) {
4653 if (!destructListeners.has(db.name)) {
4654 destructListeners.set(db.name, []);
4655 }
4656 destructListeners.get(db.name).push(db);
4657 });
4658
4659 Pouch.on('unref', function onConstructorUnref(db) {
4660 if (!destructListeners.has(db.name)) {
4661 return;
4662 }
4663 var dbList = destructListeners.get(db.name);
4664 var pos = dbList.indexOf(db);
4665 if (pos < 0) {
4666 /* istanbul ignore next */
4667 return;
4668 }
4669 dbList.splice(pos, 1);
4670 if (dbList.length > 1) {
4671 /* istanbul ignore next */
4672 destructListeners.set(db.name, dbList);
4673 } else {
4674 destructListeners["delete"](db.name);
4675 }
4676 });
4677
4678 Pouch.on('destroyed', function onConstructorDestroyed(name) {
4679 if (!destructListeners.has(name)) {
4680 return;
4681 }
4682 var dbList = destructListeners.get(name);
4683 destructListeners["delete"](name);
4684 dbList.forEach(function (db) {
4685 db.emit('destroyed',true);
4686 });
4687 });
4688}
4689
4690setUpEventEmitter(PouchDB);
4691
4692PouchDB.adapter = function (id, obj, addToPreferredAdapters) {
4693 /* istanbul ignore else */
4694 if (obj.valid()) {
4695 PouchDB.adapters[id] = obj;
4696 if (addToPreferredAdapters) {
4697 PouchDB.preferredAdapters.push(id);
4698 }
4699 }
4700};
4701
4702PouchDB.plugin = function (obj) {
4703 if (typeof obj === 'function') { // function style for plugins
4704 obj(PouchDB);
4705 } else if (typeof obj !== 'object' || Object.keys(obj).length === 0) {
4706 throw new Error('Invalid plugin: got "' + obj + '", expected an object or a function');
4707 } else {
4708 Object.keys(obj).forEach(function (id) { // object style for plugins
4709 PouchDB.prototype[id] = obj[id];
4710 });
4711 }
4712 if (this.__defaults) {
4713 PouchDB.__defaults = $inject_Object_assign({}, this.__defaults);
4714 }
4715 return PouchDB;
4716};
4717
4718PouchDB.defaults = function (defaultOpts) {
4719 function PouchAlt(name, opts) {
4720 if (!(this instanceof PouchAlt)) {
4721 return new PouchAlt(name, opts);
4722 }
4723
4724 opts = opts || {};
4725
4726 if (name && typeof name === 'object') {
4727 opts = name;
4728 name = opts.name;
4729 delete opts.name;
4730 }
4731
4732 opts = $inject_Object_assign({}, PouchAlt.__defaults, opts);
4733 PouchDB.call(this, name, opts);
4734 }
4735
4736 inherits(PouchAlt, PouchDB);
4737
4738 PouchAlt.preferredAdapters = PouchDB.preferredAdapters.slice();
4739 Object.keys(PouchDB).forEach(function (key) {
4740 if (!(key in PouchAlt)) {
4741 PouchAlt[key] = PouchDB[key];
4742 }
4743 });
4744
4745 // make default options transitive
4746 // https://github.com/pouchdb/pouchdb/issues/5922
4747 PouchAlt.__defaults = $inject_Object_assign({}, this.__defaults, defaultOpts);
4748
4749 return PouchAlt;
4750};
4751
4752PouchDB.fetch = function (url, opts) {
4753 return f$1(url, opts);
4754};
4755
4756// managed automatically by set-version.js
4757var version = "7.1.1";
4758
4759// this would just be "return doc[field]", but fields
4760// can be "deep" due to dot notation
4761function getFieldFromDoc(doc, parsedField) {
4762 var value = doc;
4763 for (var i = 0, len = parsedField.length; i < len; i++) {
4764 var key = parsedField[i];
4765 value = value[key];
4766 if (!value) {
4767 break;
4768 }
4769 }
4770 return value;
4771}
4772
4773function compare$1(left, right) {
4774 return left < right ? -1 : left > right ? 1 : 0;
4775}
4776
4777// Converts a string in dot notation to an array of its components, with backslash escaping
4778function parseField(fieldName) {
4779 // fields may be deep (e.g. "foo.bar.baz"), so parse
4780 var fields = [];
4781 var current = '';
4782 for (var i = 0, len = fieldName.length; i < len; i++) {
4783 var ch = fieldName[i];
4784 if (ch === '.') {
4785 if (i > 0 && fieldName[i - 1] === '\\') { // escaped delimiter
4786 current = current.substring(0, current.length - 1) + '.';
4787 } else { // not escaped, so delimiter
4788 fields.push(current);
4789 current = '';
4790 }
4791 } else { // normal character
4792 current += ch;
4793 }
4794 }
4795 fields.push(current);
4796 return fields;
4797}
4798
4799var combinationFields = ['$or', '$nor', '$not'];
4800function isCombinationalField(field) {
4801 return combinationFields.indexOf(field) > -1;
4802}
4803
4804function getKey(obj) {
4805 return Object.keys(obj)[0];
4806}
4807
4808function getValue(obj) {
4809 return obj[getKey(obj)];
4810}
4811
4812
4813// flatten an array of selectors joined by an $and operator
4814function mergeAndedSelectors(selectors) {
4815
4816 // sort to ensure that e.g. if the user specified
4817 // $and: [{$gt: 'a'}, {$gt: 'b'}], then it's collapsed into
4818 // just {$gt: 'b'}
4819 var res = {};
4820
4821 selectors.forEach(function (selector) {
4822 Object.keys(selector).forEach(function (field) {
4823 var matcher = selector[field];
4824 if (typeof matcher !== 'object') {
4825 matcher = {$eq: matcher};
4826 }
4827
4828 if (isCombinationalField(field)) {
4829 if (matcher instanceof Array) {
4830 res[field] = matcher.map(function (m) {
4831 return mergeAndedSelectors([m]);
4832 });
4833 } else {
4834 res[field] = mergeAndedSelectors([matcher]);
4835 }
4836 } else {
4837 var fieldMatchers = res[field] = res[field] || {};
4838 Object.keys(matcher).forEach(function (operator) {
4839 var value = matcher[operator];
4840
4841 if (operator === '$gt' || operator === '$gte') {
4842 return mergeGtGte(operator, value, fieldMatchers);
4843 } else if (operator === '$lt' || operator === '$lte') {
4844 return mergeLtLte(operator, value, fieldMatchers);
4845 } else if (operator === '$ne') {
4846 return mergeNe(value, fieldMatchers);
4847 } else if (operator === '$eq') {
4848 return mergeEq(value, fieldMatchers);
4849 }
4850 fieldMatchers[operator] = value;
4851 });
4852 }
4853 });
4854 });
4855
4856 return res;
4857}
4858
4859
4860
4861// collapse logically equivalent gt/gte values
4862function mergeGtGte(operator, value, fieldMatchers) {
4863 if (typeof fieldMatchers.$eq !== 'undefined') {
4864 return; // do nothing
4865 }
4866 if (typeof fieldMatchers.$gte !== 'undefined') {
4867 if (operator === '$gte') {
4868 if (value > fieldMatchers.$gte) { // more specificity
4869 fieldMatchers.$gte = value;
4870 }
4871 } else { // operator === '$gt'
4872 if (value >= fieldMatchers.$gte) { // more specificity
4873 delete fieldMatchers.$gte;
4874 fieldMatchers.$gt = value;
4875 }
4876 }
4877 } else if (typeof fieldMatchers.$gt !== 'undefined') {
4878 if (operator === '$gte') {
4879 if (value > fieldMatchers.$gt) { // more specificity
4880 delete fieldMatchers.$gt;
4881 fieldMatchers.$gte = value;
4882 }
4883 } else { // operator === '$gt'
4884 if (value > fieldMatchers.$gt) { // more specificity
4885 fieldMatchers.$gt = value;
4886 }
4887 }
4888 } else {
4889 fieldMatchers[operator] = value;
4890 }
4891}
4892
4893// collapse logically equivalent lt/lte values
4894function mergeLtLte(operator, value, fieldMatchers) {
4895 if (typeof fieldMatchers.$eq !== 'undefined') {
4896 return; // do nothing
4897 }
4898 if (typeof fieldMatchers.$lte !== 'undefined') {
4899 if (operator === '$lte') {
4900 if (value < fieldMatchers.$lte) { // more specificity
4901 fieldMatchers.$lte = value;
4902 }
4903 } else { // operator === '$gt'
4904 if (value <= fieldMatchers.$lte) { // more specificity
4905 delete fieldMatchers.$lte;
4906 fieldMatchers.$lt = value;
4907 }
4908 }
4909 } else if (typeof fieldMatchers.$lt !== 'undefined') {
4910 if (operator === '$lte') {
4911 if (value < fieldMatchers.$lt) { // more specificity
4912 delete fieldMatchers.$lt;
4913 fieldMatchers.$lte = value;
4914 }
4915 } else { // operator === '$gt'
4916 if (value < fieldMatchers.$lt) { // more specificity
4917 fieldMatchers.$lt = value;
4918 }
4919 }
4920 } else {
4921 fieldMatchers[operator] = value;
4922 }
4923}
4924
4925// combine $ne values into one array
4926function mergeNe(value, fieldMatchers) {
4927 if ('$ne' in fieldMatchers) {
4928 // there are many things this could "not" be
4929 fieldMatchers.$ne.push(value);
4930 } else { // doesn't exist yet
4931 fieldMatchers.$ne = [value];
4932 }
4933}
4934
4935// add $eq into the mix
4936function mergeEq(value, fieldMatchers) {
4937 // these all have less specificity than the $eq
4938 // TODO: check for user errors here
4939 delete fieldMatchers.$gt;
4940 delete fieldMatchers.$gte;
4941 delete fieldMatchers.$lt;
4942 delete fieldMatchers.$lte;
4943 delete fieldMatchers.$ne;
4944 fieldMatchers.$eq = value;
4945}
4946
4947//#7458: execute function mergeAndedSelectors on nested $and
4948function mergeAndedSelectorsNested(obj) {
4949 for (var prop in obj) {
4950 if (Array.isArray(obj)) {
4951 for (var i in obj) {
4952 if (obj[i]['$and']) {
4953 obj[i] = mergeAndedSelectors(obj[i]['$and']);
4954 }
4955 }
4956 }
4957 var value = obj[prop];
4958 if (typeof value === 'object') {
4959 mergeAndedSelectorsNested(value); // <- recursive call
4960 }
4961 }
4962 return obj;
4963}
4964
4965//#7458: determine id $and is present in selector (at any level)
4966function isAndInSelector(obj, isAnd) {
4967 for (var prop in obj) {
4968 if (prop === '$and') {
4969 isAnd = true;
4970 }
4971 var value = obj[prop];
4972 if (typeof value === 'object') {
4973 isAnd = isAndInSelector(value, isAnd); // <- recursive call
4974 }
4975 }
4976 return isAnd;
4977}
4978
4979//
4980// normalize the selector
4981//
4982function massageSelector(input) {
4983 var result = clone(input);
4984 var wasAnded = false;
4985 //#7458: if $and is present in selector (at any level) merge nested $and
4986 if (isAndInSelector(result, false)) {
4987 result = mergeAndedSelectorsNested(result);
4988 if ('$and' in result) {
4989 result = mergeAndedSelectors(result['$and']);
4990 }
4991 wasAnded = true;
4992 }
4993
4994 ['$or', '$nor'].forEach(function (orOrNor) {
4995 if (orOrNor in result) {
4996 // message each individual selector
4997 // e.g. {foo: 'bar'} becomes {foo: {$eq: 'bar'}}
4998 result[orOrNor].forEach(function (subSelector) {
4999 var fields = Object.keys(subSelector);
5000 for (var i = 0; i < fields.length; i++) {
5001 var field = fields[i];
5002 var matcher = subSelector[field];
5003 if (typeof matcher !== 'object' || matcher === null) {
5004 subSelector[field] = {$eq: matcher};
5005 }
5006 }
5007 });
5008 }
5009 });
5010
5011 if ('$not' in result) {
5012 //This feels a little like forcing, but it will work for now,
5013 //I would like to come back to this and make the merging of selectors a little more generic
5014 result['$not'] = mergeAndedSelectors([result['$not']]);
5015 }
5016
5017 var fields = Object.keys(result);
5018
5019 for (var i = 0; i < fields.length; i++) {
5020 var field = fields[i];
5021 var matcher = result[field];
5022
5023 if (typeof matcher !== 'object' || matcher === null) {
5024 matcher = {$eq: matcher};
5025 } else if ('$ne' in matcher && !wasAnded) {
5026 // I put these in an array, since there may be more than one
5027 // but in the "mergeAnded" operation, I already take care of that
5028 matcher.$ne = [matcher.$ne];
5029 }
5030 result[field] = matcher;
5031 }
5032
5033 return result;
5034}
5035
5036function pad(str, padWith, upToLength) {
5037 var padding = '';
5038 var targetLength = upToLength - str.length;
5039 /* istanbul ignore next */
5040 while (padding.length < targetLength) {
5041 padding += padWith;
5042 }
5043 return padding;
5044}
5045
5046function padLeft(str, padWith, upToLength) {
5047 var padding = pad(str, padWith, upToLength);
5048 return padding + str;
5049}
5050
5051var MIN_MAGNITUDE = -324; // verified by -Number.MIN_VALUE
5052var MAGNITUDE_DIGITS = 3; // ditto
5053var SEP = ''; // set to '_' for easier debugging
5054
5055function collate(a, b) {
5056
5057 if (a === b) {
5058 return 0;
5059 }
5060
5061 a = normalizeKey(a);
5062 b = normalizeKey(b);
5063
5064 var ai = collationIndex(a);
5065 var bi = collationIndex(b);
5066 if ((ai - bi) !== 0) {
5067 return ai - bi;
5068 }
5069 switch (typeof a) {
5070 case 'number':
5071 return a - b;
5072 case 'boolean':
5073 return a < b ? -1 : 1;
5074 case 'string':
5075 return stringCollate(a, b);
5076 }
5077 return Array.isArray(a) ? arrayCollate(a, b) : objectCollate(a, b);
5078}
5079
5080// couch considers null/NaN/Infinity/-Infinity === undefined,
5081// for the purposes of mapreduce indexes. also, dates get stringified.
5082function normalizeKey(key) {
5083 switch (typeof key) {
5084 case 'undefined':
5085 return null;
5086 case 'number':
5087 if (key === Infinity || key === -Infinity || isNaN(key)) {
5088 return null;
5089 }
5090 return key;
5091 case 'object':
5092 var origKey = key;
5093 if (Array.isArray(key)) {
5094 var len = key.length;
5095 key = new Array(len);
5096 for (var i = 0; i < len; i++) {
5097 key[i] = normalizeKey(origKey[i]);
5098 }
5099 /* istanbul ignore next */
5100 } else if (key instanceof Date) {
5101 return key.toJSON();
5102 } else if (key !== null) { // generic object
5103 key = {};
5104 for (var k in origKey) {
5105 if (origKey.hasOwnProperty(k)) {
5106 var val = origKey[k];
5107 if (typeof val !== 'undefined') {
5108 key[k] = normalizeKey(val);
5109 }
5110 }
5111 }
5112 }
5113 }
5114 return key;
5115}
5116
5117function indexify(key) {
5118 if (key !== null) {
5119 switch (typeof key) {
5120 case 'boolean':
5121 return key ? 1 : 0;
5122 case 'number':
5123 return numToIndexableString(key);
5124 case 'string':
5125 // We've to be sure that key does not contain \u0000
5126 // Do order-preserving replacements:
5127 // 0 -> 1, 1
5128 // 1 -> 1, 2
5129 // 2 -> 2, 2
5130 /* eslint-disable no-control-regex */
5131 return key
5132 .replace(/\u0002/g, '\u0002\u0002')
5133 .replace(/\u0001/g, '\u0001\u0002')
5134 .replace(/\u0000/g, '\u0001\u0001');
5135 /* eslint-enable no-control-regex */
5136 case 'object':
5137 var isArray = Array.isArray(key);
5138 var arr = isArray ? key : Object.keys(key);
5139 var i = -1;
5140 var len = arr.length;
5141 var result = '';
5142 if (isArray) {
5143 while (++i < len) {
5144 result += toIndexableString(arr[i]);
5145 }
5146 } else {
5147 while (++i < len) {
5148 var objKey = arr[i];
5149 result += toIndexableString(objKey) +
5150 toIndexableString(key[objKey]);
5151 }
5152 }
5153 return result;
5154 }
5155 }
5156 return '';
5157}
5158
5159// convert the given key to a string that would be appropriate
5160// for lexical sorting, e.g. within a database, where the
5161// sorting is the same given by the collate() function.
5162function toIndexableString(key) {
5163 var zero = '\u0000';
5164 key = normalizeKey(key);
5165 return collationIndex(key) + SEP + indexify(key) + zero;
5166}
5167
5168function parseNumber(str, i) {
5169 var originalIdx = i;
5170 var num;
5171 var zero = str[i] === '1';
5172 if (zero) {
5173 num = 0;
5174 i++;
5175 } else {
5176 var neg = str[i] === '0';
5177 i++;
5178 var numAsString = '';
5179 var magAsString = str.substring(i, i + MAGNITUDE_DIGITS);
5180 var magnitude = parseInt(magAsString, 10) + MIN_MAGNITUDE;
5181 /* istanbul ignore next */
5182 if (neg) {
5183 magnitude = -magnitude;
5184 }
5185 i += MAGNITUDE_DIGITS;
5186 while (true) {
5187 var ch = str[i];
5188 if (ch === '\u0000') {
5189 break;
5190 } else {
5191 numAsString += ch;
5192 }
5193 i++;
5194 }
5195 numAsString = numAsString.split('.');
5196 if (numAsString.length === 1) {
5197 num = parseInt(numAsString, 10);
5198 } else {
5199 /* istanbul ignore next */
5200 num = parseFloat(numAsString[0] + '.' + numAsString[1]);
5201 }
5202 /* istanbul ignore next */
5203 if (neg) {
5204 num = num - 10;
5205 }
5206 /* istanbul ignore next */
5207 if (magnitude !== 0) {
5208 // parseFloat is more reliable than pow due to rounding errors
5209 // e.g. Number.MAX_VALUE would return Infinity if we did
5210 // num * Math.pow(10, magnitude);
5211 num = parseFloat(num + 'e' + magnitude);
5212 }
5213 }
5214 return {num: num, length : i - originalIdx};
5215}
5216
5217// move up the stack while parsing
5218// this function moved outside of parseIndexableString for performance
5219function pop(stack, metaStack) {
5220 var obj = stack.pop();
5221
5222 if (metaStack.length) {
5223 var lastMetaElement = metaStack[metaStack.length - 1];
5224 if (obj === lastMetaElement.element) {
5225 // popping a meta-element, e.g. an object whose value is another object
5226 metaStack.pop();
5227 lastMetaElement = metaStack[metaStack.length - 1];
5228 }
5229 var element = lastMetaElement.element;
5230 var lastElementIndex = lastMetaElement.index;
5231 if (Array.isArray(element)) {
5232 element.push(obj);
5233 } else if (lastElementIndex === stack.length - 2) { // obj with key+value
5234 var key = stack.pop();
5235 element[key] = obj;
5236 } else {
5237 stack.push(obj); // obj with key only
5238 }
5239 }
5240}
5241
5242function parseIndexableString(str) {
5243 var stack = [];
5244 var metaStack = []; // stack for arrays and objects
5245 var i = 0;
5246
5247 /*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
5248 while (true) {
5249 var collationIndex = str[i++];
5250 if (collationIndex === '\u0000') {
5251 if (stack.length === 1) {
5252 return stack.pop();
5253 } else {
5254 pop(stack, metaStack);
5255 continue;
5256 }
5257 }
5258 switch (collationIndex) {
5259 case '1':
5260 stack.push(null);
5261 break;
5262 case '2':
5263 stack.push(str[i] === '1');
5264 i++;
5265 break;
5266 case '3':
5267 var parsedNum = parseNumber(str, i);
5268 stack.push(parsedNum.num);
5269 i += parsedNum.length;
5270 break;
5271 case '4':
5272 var parsedStr = '';
5273 /*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
5274 while (true) {
5275 var ch = str[i];
5276 if (ch === '\u0000') {
5277 break;
5278 }
5279 parsedStr += ch;
5280 i++;
5281 }
5282 // perform the reverse of the order-preserving replacement
5283 // algorithm (see above)
5284 /* eslint-disable no-control-regex */
5285 parsedStr = parsedStr.replace(/\u0001\u0001/g, '\u0000')
5286 .replace(/\u0001\u0002/g, '\u0001')
5287 .replace(/\u0002\u0002/g, '\u0002');
5288 /* eslint-enable no-control-regex */
5289 stack.push(parsedStr);
5290 break;
5291 case '5':
5292 var arrayElement = { element: [], index: stack.length };
5293 stack.push(arrayElement.element);
5294 metaStack.push(arrayElement);
5295 break;
5296 case '6':
5297 var objElement = { element: {}, index: stack.length };
5298 stack.push(objElement.element);
5299 metaStack.push(objElement);
5300 break;
5301 /* istanbul ignore next */
5302 default:
5303 throw new Error(
5304 'bad collationIndex or unexpectedly reached end of input: ' +
5305 collationIndex);
5306 }
5307 }
5308}
5309
5310function arrayCollate(a, b) {
5311 var len = Math.min(a.length, b.length);
5312 for (var i = 0; i < len; i++) {
5313 var sort = collate(a[i], b[i]);
5314 if (sort !== 0) {
5315 return sort;
5316 }
5317 }
5318 return (a.length === b.length) ? 0 :
5319 (a.length > b.length) ? 1 : -1;
5320}
5321function stringCollate(a, b) {
5322 // See: https://github.com/daleharvey/pouchdb/issues/40
5323 // This is incompatible with the CouchDB implementation, but its the
5324 // best we can do for now
5325 return (a === b) ? 0 : ((a > b) ? 1 : -1);
5326}
5327function objectCollate(a, b) {
5328 var ak = Object.keys(a), bk = Object.keys(b);
5329 var len = Math.min(ak.length, bk.length);
5330 for (var i = 0; i < len; i++) {
5331 // First sort the keys
5332 var sort = collate(ak[i], bk[i]);
5333 if (sort !== 0) {
5334 return sort;
5335 }
5336 // if the keys are equal sort the values
5337 sort = collate(a[ak[i]], b[bk[i]]);
5338 if (sort !== 0) {
5339 return sort;
5340 }
5341
5342 }
5343 return (ak.length === bk.length) ? 0 :
5344 (ak.length > bk.length) ? 1 : -1;
5345}
5346// The collation is defined by erlangs ordered terms
5347// the atoms null, true, false come first, then numbers, strings,
5348// arrays, then objects
5349// null/undefined/NaN/Infinity/-Infinity are all considered null
5350function collationIndex(x) {
5351 var id = ['boolean', 'number', 'string', 'object'];
5352 var idx = id.indexOf(typeof x);
5353 //false if -1 otherwise true, but fast!!!!1
5354 if (~idx) {
5355 if (x === null) {
5356 return 1;
5357 }
5358 if (Array.isArray(x)) {
5359 return 5;
5360 }
5361 return idx < 3 ? (idx + 2) : (idx + 3);
5362 }
5363 /* istanbul ignore next */
5364 if (Array.isArray(x)) {
5365 return 5;
5366 }
5367}
5368
5369// conversion:
5370// x yyy zz...zz
5371// x = 0 for negative, 1 for 0, 2 for positive
5372// y = exponent (for negative numbers negated) moved so that it's >= 0
5373// z = mantisse
5374function numToIndexableString(num) {
5375
5376 if (num === 0) {
5377 return '1';
5378 }
5379
5380 // convert number to exponential format for easier and
5381 // more succinct string sorting
5382 var expFormat = num.toExponential().split(/e\+?/);
5383 var magnitude = parseInt(expFormat[1], 10);
5384
5385 var neg = num < 0;
5386
5387 var result = neg ? '0' : '2';
5388
5389 // first sort by magnitude
5390 // it's easier if all magnitudes are positive
5391 var magForComparison = ((neg ? -magnitude : magnitude) - MIN_MAGNITUDE);
5392 var magString = padLeft((magForComparison).toString(), '0', MAGNITUDE_DIGITS);
5393
5394 result += SEP + magString;
5395
5396 // then sort by the factor
5397 var factor = Math.abs(parseFloat(expFormat[0])); // [1..10)
5398 /* istanbul ignore next */
5399 if (neg) { // for negative reverse ordering
5400 factor = 10 - factor;
5401 }
5402
5403 var factorStr = factor.toFixed(20);
5404
5405 // strip zeros from the end
5406 factorStr = factorStr.replace(/\.?0+$/, '');
5407
5408 result += SEP + factorStr;
5409
5410 return result;
5411}
5412
5413// create a comparator based on the sort object
5414function createFieldSorter(sort) {
5415
5416 function getFieldValuesAsArray(doc) {
5417 return sort.map(function (sorting) {
5418 var fieldName = getKey(sorting);
5419 var parsedField = parseField(fieldName);
5420 var docFieldValue = getFieldFromDoc(doc, parsedField);
5421 return docFieldValue;
5422 });
5423 }
5424
5425 return function (aRow, bRow) {
5426 var aFieldValues = getFieldValuesAsArray(aRow.doc);
5427 var bFieldValues = getFieldValuesAsArray(bRow.doc);
5428 var collation = collate(aFieldValues, bFieldValues);
5429 if (collation !== 0) {
5430 return collation;
5431 }
5432 // this is what mango seems to do
5433 return compare$1(aRow.doc._id, bRow.doc._id);
5434 };
5435}
5436
5437function filterInMemoryFields(rows, requestDef, inMemoryFields) {
5438 rows = rows.filter(function (row) {
5439 return rowFilter(row.doc, requestDef.selector, inMemoryFields);
5440 });
5441
5442 if (requestDef.sort) {
5443 // in-memory sort
5444 var fieldSorter = createFieldSorter(requestDef.sort);
5445 rows = rows.sort(fieldSorter);
5446 if (typeof requestDef.sort[0] !== 'string' &&
5447 getValue(requestDef.sort[0]) === 'desc') {
5448 rows = rows.reverse();
5449 }
5450 }
5451
5452 if ('limit' in requestDef || 'skip' in requestDef) {
5453 // have to do the limit in-memory
5454 var skip = requestDef.skip || 0;
5455 var limit = ('limit' in requestDef ? requestDef.limit : rows.length) + skip;
5456 rows = rows.slice(skip, limit);
5457 }
5458 return rows;
5459}
5460
5461function rowFilter(doc, selector, inMemoryFields) {
5462 return inMemoryFields.every(function (field) {
5463 var matcher = selector[field];
5464 var parsedField = parseField(field);
5465 var docFieldValue = getFieldFromDoc(doc, parsedField);
5466 if (isCombinationalField(field)) {
5467 return matchCominationalSelector(field, matcher, doc);
5468 }
5469
5470 return matchSelector(matcher, doc, parsedField, docFieldValue);
5471 });
5472}
5473
5474function matchSelector(matcher, doc, parsedField, docFieldValue) {
5475 if (!matcher) {
5476 // no filtering necessary; this field is just needed for sorting
5477 return true;
5478 }
5479
5480 // is matcher an object, if so continue recursion
5481 if (typeof matcher === 'object') {
5482 return Object.keys(matcher).every(function (userOperator) {
5483 var userValue = matcher[userOperator];
5484 return match(userOperator, doc, userValue, parsedField, docFieldValue);
5485 });
5486 }
5487
5488 // no more depth, No need to recurse further
5489 return matcher === docFieldValue;
5490}
5491
5492function matchCominationalSelector(field, matcher, doc) {
5493
5494 if (field === '$or') {
5495 return matcher.some(function (orMatchers) {
5496 return rowFilter(doc, orMatchers, Object.keys(orMatchers));
5497 });
5498 }
5499
5500 if (field === '$not') {
5501 return !rowFilter(doc, matcher, Object.keys(matcher));
5502 }
5503
5504 //`$nor`
5505 return !matcher.find(function (orMatchers) {
5506 return rowFilter(doc, orMatchers, Object.keys(orMatchers));
5507 });
5508
5509}
5510
5511function match(userOperator, doc, userValue, parsedField, docFieldValue) {
5512 if (!matchers[userOperator]) {
5513 throw new Error('unknown operator "' + userOperator +
5514 '" - should be one of $eq, $lte, $lt, $gt, $gte, $exists, $ne, $in, ' +
5515 '$nin, $size, $mod, $regex, $elemMatch, $type, $allMatch or $all');
5516 }
5517 return matchers[userOperator](doc, userValue, parsedField, docFieldValue);
5518}
5519
5520function fieldExists(docFieldValue) {
5521 return typeof docFieldValue !== 'undefined' && docFieldValue !== null;
5522}
5523
5524function fieldIsNotUndefined(docFieldValue) {
5525 return typeof docFieldValue !== 'undefined';
5526}
5527
5528function modField(docFieldValue, userValue) {
5529 var divisor = userValue[0];
5530 var mod = userValue[1];
5531 if (divisor === 0) {
5532 throw new Error('Bad divisor, cannot divide by zero');
5533 }
5534
5535 if (parseInt(divisor, 10) !== divisor ) {
5536 throw new Error('Divisor is not an integer');
5537 }
5538
5539 if (parseInt(mod, 10) !== mod ) {
5540 throw new Error('Modulus is not an integer');
5541 }
5542
5543 if (parseInt(docFieldValue, 10) !== docFieldValue) {
5544 return false;
5545 }
5546
5547 return docFieldValue % divisor === mod;
5548}
5549
5550function arrayContainsValue(docFieldValue, userValue) {
5551 return userValue.some(function (val) {
5552 if (docFieldValue instanceof Array) {
5553 return docFieldValue.indexOf(val) > -1;
5554 }
5555
5556 return docFieldValue === val;
5557 });
5558}
5559
5560function arrayContainsAllValues(docFieldValue, userValue) {
5561 return userValue.every(function (val) {
5562 return docFieldValue.indexOf(val) > -1;
5563 });
5564}
5565
5566function arraySize(docFieldValue, userValue) {
5567 return docFieldValue.length === userValue;
5568}
5569
5570function regexMatch(docFieldValue, userValue) {
5571 var re = new RegExp(userValue);
5572
5573 return re.test(docFieldValue);
5574}
5575
5576function typeMatch(docFieldValue, userValue) {
5577
5578 switch (userValue) {
5579 case 'null':
5580 return docFieldValue === null;
5581 case 'boolean':
5582 return typeof (docFieldValue) === 'boolean';
5583 case 'number':
5584 return typeof (docFieldValue) === 'number';
5585 case 'string':
5586 return typeof (docFieldValue) === 'string';
5587 case 'array':
5588 return docFieldValue instanceof Array;
5589 case 'object':
5590 return ({}).toString.call(docFieldValue) === '[object Object]';
5591 }
5592
5593 throw new Error(userValue + ' not supported as a type.' +
5594 'Please use one of object, string, array, number, boolean or null.');
5595
5596}
5597
5598var matchers = {
5599
5600 '$elemMatch': function (doc, userValue, parsedField, docFieldValue) {
5601 if (!Array.isArray(docFieldValue)) {
5602 return false;
5603 }
5604
5605 if (docFieldValue.length === 0) {
5606 return false;
5607 }
5608
5609 if (typeof docFieldValue[0] === 'object') {
5610 return docFieldValue.some(function (val) {
5611 return rowFilter(val, userValue, Object.keys(userValue));
5612 });
5613 }
5614
5615 return docFieldValue.some(function (val) {
5616 return matchSelector(userValue, doc, parsedField, val);
5617 });
5618 },
5619
5620 '$allMatch': function (doc, userValue, parsedField, docFieldValue) {
5621 if (!Array.isArray(docFieldValue)) {
5622 return false;
5623 }
5624
5625 /* istanbul ignore next */
5626 if (docFieldValue.length === 0) {
5627 return false;
5628 }
5629
5630 if (typeof docFieldValue[0] === 'object') {
5631 return docFieldValue.every(function (val) {
5632 return rowFilter(val, userValue, Object.keys(userValue));
5633 });
5634 }
5635
5636 return docFieldValue.every(function (val) {
5637 return matchSelector(userValue, doc, parsedField, val);
5638 });
5639 },
5640
5641 '$eq': function (doc, userValue, parsedField, docFieldValue) {
5642 return fieldIsNotUndefined(docFieldValue) && collate(docFieldValue, userValue) === 0;
5643 },
5644
5645 '$gte': function (doc, userValue, parsedField, docFieldValue) {
5646 return fieldIsNotUndefined(docFieldValue) && collate(docFieldValue, userValue) >= 0;
5647 },
5648
5649 '$gt': function (doc, userValue, parsedField, docFieldValue) {
5650 return fieldIsNotUndefined(docFieldValue) && collate(docFieldValue, userValue) > 0;
5651 },
5652
5653 '$lte': function (doc, userValue, parsedField, docFieldValue) {
5654 return fieldIsNotUndefined(docFieldValue) && collate(docFieldValue, userValue) <= 0;
5655 },
5656
5657 '$lt': function (doc, userValue, parsedField, docFieldValue) {
5658 return fieldIsNotUndefined(docFieldValue) && collate(docFieldValue, userValue) < 0;
5659 },
5660
5661 '$exists': function (doc, userValue, parsedField, docFieldValue) {
5662 //a field that is null is still considered to exist
5663 if (userValue) {
5664 return fieldIsNotUndefined(docFieldValue);
5665 }
5666
5667 return !fieldIsNotUndefined(docFieldValue);
5668 },
5669
5670 '$mod': function (doc, userValue, parsedField, docFieldValue) {
5671 return fieldExists(docFieldValue) && modField(docFieldValue, userValue);
5672 },
5673
5674 '$ne': function (doc, userValue, parsedField, docFieldValue) {
5675 return userValue.every(function (neValue) {
5676 return collate(docFieldValue, neValue) !== 0;
5677 });
5678 },
5679 '$in': function (doc, userValue, parsedField, docFieldValue) {
5680 return fieldExists(docFieldValue) && arrayContainsValue(docFieldValue, userValue);
5681 },
5682
5683 '$nin': function (doc, userValue, parsedField, docFieldValue) {
5684 return fieldExists(docFieldValue) && !arrayContainsValue(docFieldValue, userValue);
5685 },
5686
5687 '$size': function (doc, userValue, parsedField, docFieldValue) {
5688 return fieldExists(docFieldValue) && arraySize(docFieldValue, userValue);
5689 },
5690
5691 '$all': function (doc, userValue, parsedField, docFieldValue) {
5692 return Array.isArray(docFieldValue) && arrayContainsAllValues(docFieldValue, userValue);
5693 },
5694
5695 '$regex': function (doc, userValue, parsedField, docFieldValue) {
5696 return fieldExists(docFieldValue) && regexMatch(docFieldValue, userValue);
5697 },
5698
5699 '$type': function (doc, userValue, parsedField, docFieldValue) {
5700 return typeMatch(docFieldValue, userValue);
5701 }
5702};
5703
5704// return true if the given doc matches the supplied selector
5705function matchesSelector(doc, selector) {
5706 /* istanbul ignore if */
5707 if (typeof selector !== 'object') {
5708 // match the CouchDB error message
5709 throw new Error('Selector error: expected a JSON object');
5710 }
5711
5712 selector = massageSelector(selector);
5713 var row = {
5714 'doc': doc
5715 };
5716
5717 var rowsMatched = filterInMemoryFields([row], { 'selector': selector }, Object.keys(selector));
5718 return rowsMatched && rowsMatched.length === 1;
5719}
5720
5721function evalFilter(input) {
5722 return scopeEval('"use strict";\nreturn ' + input + ';', {});
5723}
5724
5725function evalView(input) {
5726 var code = [
5727 'return function(doc) {',
5728 ' "use strict";',
5729 ' var emitted = false;',
5730 ' var emit = function (a, b) {',
5731 ' emitted = true;',
5732 ' };',
5733 ' var view = ' + input + ';',
5734 ' view(doc);',
5735 ' if (emitted) {',
5736 ' return true;',
5737 ' }',
5738 '};'
5739 ].join('\n');
5740
5741 return scopeEval(code, {});
5742}
5743
5744function validate(opts, callback) {
5745 if (opts.selector) {
5746 if (opts.filter && opts.filter !== '_selector') {
5747 var filterName = typeof opts.filter === 'string' ?
5748 opts.filter : 'function';
5749 return callback(new Error('selector invalid for filter "' + filterName + '"'));
5750 }
5751 }
5752 callback();
5753}
5754
5755function normalize(opts) {
5756 if (opts.view && !opts.filter) {
5757 opts.filter = '_view';
5758 }
5759
5760 if (opts.selector && !opts.filter) {
5761 opts.filter = '_selector';
5762 }
5763
5764 if (opts.filter && typeof opts.filter === 'string') {
5765 if (opts.filter === '_view') {
5766 opts.view = normalizeDesignDocFunctionName(opts.view);
5767 } else {
5768 opts.filter = normalizeDesignDocFunctionName(opts.filter);
5769 }
5770 }
5771}
5772
5773function shouldFilter(changesHandler, opts) {
5774 return opts.filter && typeof opts.filter === 'string' &&
5775 !opts.doc_ids && !isRemote(changesHandler.db);
5776}
5777
5778function filter(changesHandler, opts) {
5779 var callback = opts.complete;
5780 if (opts.filter === '_view') {
5781 if (!opts.view || typeof opts.view !== 'string') {
5782 var err = createError(BAD_REQUEST,
5783 '`view` filter parameter not found or invalid.');
5784 return callback(err);
5785 }
5786 // fetch a view from a design doc, make it behave like a filter
5787 var viewName = parseDesignDocFunctionName(opts.view);
5788 changesHandler.db.get('_design/' + viewName[0], function (err, ddoc) {
5789 /* istanbul ignore if */
5790 if (changesHandler.isCancelled) {
5791 return callback(null, {status: 'cancelled'});
5792 }
5793 /* istanbul ignore next */
5794 if (err) {
5795 return callback(generateErrorFromResponse(err));
5796 }
5797 var mapFun = ddoc && ddoc.views && ddoc.views[viewName[1]] &&
5798 ddoc.views[viewName[1]].map;
5799 if (!mapFun) {
5800 return callback(createError(MISSING_DOC,
5801 (ddoc.views ? 'missing json key: ' + viewName[1] :
5802 'missing json key: views')));
5803 }
5804 opts.filter = evalView(mapFun);
5805 changesHandler.doChanges(opts);
5806 });
5807 } else if (opts.selector) {
5808 opts.filter = function (doc) {
5809 return matchesSelector(doc, opts.selector);
5810 };
5811 changesHandler.doChanges(opts);
5812 } else {
5813 // fetch a filter from a design doc
5814 var filterName = parseDesignDocFunctionName(opts.filter);
5815 changesHandler.db.get('_design/' + filterName[0], function (err, ddoc) {
5816 /* istanbul ignore if */
5817 if (changesHandler.isCancelled) {
5818 return callback(null, {status: 'cancelled'});
5819 }
5820 /* istanbul ignore next */
5821 if (err) {
5822 return callback(generateErrorFromResponse(err));
5823 }
5824 var filterFun = ddoc && ddoc.filters && ddoc.filters[filterName[1]];
5825 if (!filterFun) {
5826 return callback(createError(MISSING_DOC,
5827 ((ddoc && ddoc.filters) ? 'missing json key: ' + filterName[1]
5828 : 'missing json key: filters')));
5829 }
5830 opts.filter = evalFilter(filterFun);
5831 changesHandler.doChanges(opts);
5832 });
5833 }
5834}
5835
5836function applyChangesFilterPlugin(PouchDB) {
5837 PouchDB._changesFilterPlugin = {
5838 validate: validate,
5839 normalize: normalize,
5840 shouldFilter: shouldFilter,
5841 filter: filter
5842 };
5843}
5844
5845// TODO: remove from pouchdb-core (breaking)
5846PouchDB.plugin(applyChangesFilterPlugin);
5847
5848PouchDB.version = version;
5849
5850var IDB_NULL = Number.MIN_SAFE_INTEGER;
5851var IDB_FALSE = Number.MIN_SAFE_INTEGER + 1;
5852var IDB_TRUE = Number.MIN_SAFE_INTEGER + 2;
5853
5854//
5855// IndexedDB only allows valid JS names in its index paths, whereas JSON allows
5856// for any string at all. This converts invalid JS names to valid ones, to allow
5857// for them to be indexed.
5858//
5859// For example, "foo-bar" is a valid JSON key, but cannot be a valid JS name
5860// (because that would be read as foo minus bar).
5861//
5862// Very high level rules for valid JS names are:
5863// - First character cannot start with a number
5864// - Otherwise all characters must be be a-z, A-Z, 0-9, $ or _.
5865// - We allow . unless the name represents a single field, as that represents
5866// a deep index path.
5867//
5868// This is more aggressive than it needs to be, but also simpler.
5869//
5870var KEY_INVALID = /[^a-zA-Z0-9_$]+|(^[^a-zA-Z_$])/g;
5871var PATH_INVALID = /(\\.)|[^a-zA-Z0-9_$.]+|(^[^a-zA-Z_$])/g;
5872var SLASH = '\\'.charCodeAt(0);
5873
5874// These are the same as above but without the global flag
5875// we want to use RegExp.test because it's really fast, but the global flag
5876// makes the regex const stateful (seriously) as it walked through all instances
5877var TEST_KEY_INVALID = /[^a-zA-Z0-9_$]+|(^[^a-zA-Z_$])/;
5878var TEST_PATH_INVALID = /(\\.)|[^a-zA-Z0-9_$.]+|(^[^a-zA-Z_$])/;
5879function needsSanitise(name, isPath) {
5880 if (isPath) {
5881 return TEST_PATH_INVALID.test(name);
5882 } else {
5883 return TEST_KEY_INVALID.test(name);
5884 }
5885}
5886
5887function sanitise(name, isPath) {
5888 var correctCharacters = function (match) {
5889 var good = '';
5890 for (var i = 0; i < match.length; i++) {
5891 var code = match.charCodeAt(i);
5892 // If you're sanitising a path, a slash character is there to be interpreted
5893 // by whatever parses the path later as "escape the next thing".
5894 //
5895 // e.g., if you want to index THIS string:
5896 // {"foo": {"bar.baz": "THIS"}}
5897 // Your index path would be "foo.bar\.baz".
5898 if (code === SLASH && isPath) {
5899 continue;
5900 }
5901
5902 good += '_c' + code + '_';
5903 }
5904 return good;
5905 };
5906
5907 if (isPath) {
5908 return name.replace(PATH_INVALID, correctCharacters);
5909 } else {
5910 return name.replace(KEY_INVALID, correctCharacters);
5911 }
5912}
5913
5914function needsRewrite(data) {
5915 for (var key of Object.keys(data)) {
5916 if (needsSanitise(key)) {
5917 return true;
5918 } else if (data[key] === null || typeof data[key] === 'boolean') {
5919 return true;
5920 } else if (typeof data[key] === 'object') {
5921 return needsRewrite(data[key]);
5922 }
5923 }
5924}
5925
5926function rewrite(data) {
5927 if (!needsRewrite(data)) {
5928 return false;
5929 }
5930
5931 var isArray = Array.isArray(data);
5932 var clone = isArray
5933 ? []
5934 : {};
5935
5936 Object.keys(data).forEach(function (key) {
5937 var safeKey = isArray ? key : sanitise(key);
5938
5939 if (data[key] === null) {
5940 clone[safeKey] = IDB_NULL;
5941 } else if (typeof data[key] === 'boolean') {
5942 clone[safeKey] = data[key] ? IDB_TRUE : IDB_FALSE;
5943 } else if (typeof data[key] === 'object') {
5944 clone[safeKey] = rewrite(data[key]);
5945 } else {
5946 clone[safeKey] = data[key];
5947 }
5948 });
5949
5950 return clone;
5951}
5952
5953var DOC_STORE = 'docs';
5954var META_STORE = 'meta';
5955
5956function idbError(callback) {
5957 return function (evt) {
5958 var message = 'unknown_error';
5959 if (evt.target && evt.target.error) {
5960 message = evt.target.error.name || evt.target.error.message;
5961 }
5962 callback(createError(IDB_ERROR, message, evt.type));
5963 };
5964}
5965
5966function processAttachment(name, src, doc, isBinary) {
5967
5968 delete doc._attachments[name].stub;
5969
5970 if (isBinary) {
5971 doc._attachments[name].data =
5972 src.attachments[doc._attachments[name].digest].data;
5973 return Promise.resolve();
5974 }
5975
5976 return new Promise(function (resolve) {
5977 var data = src.attachments[doc._attachments[name].digest].data;
5978 readAsBinaryString(data, function (binString) {
5979 doc._attachments[name].data = thisBtoa(binString);
5980 delete doc._attachments[name].length;
5981 resolve();
5982 });
5983 });
5984}
5985
5986function rawIndexFields(ddoc, viewName) {
5987 // fields are an array of either the string name of the field, or a key value
5988 var fields = ddoc.views[viewName].options &&
5989 ddoc.views[viewName].options.def &&
5990 ddoc.views[viewName].options.def.fields || [];
5991
5992 // Either ['foo'] or [{'foo': 'desc'}]
5993 return fields.map(function (field) {
5994 if (typeof field === 'string') {
5995 return field;
5996 } else {
5997 return Object.keys(field)[0];
5998 }
5999 });
6000}
6001
6002function naturalIndexName(fields) {
6003 return '_find_idx/' + fields.join('/');
6004}
6005
6006/**
6007 * Convert the fields the user gave us in the view and convert them to work for
6008 * indexeddb.
6009 *
6010 * fields is an array of field strings. A field string could be one field:
6011 * 'foo'
6012 * Or it could be a json path:
6013 * 'foo.bar'
6014 */
6015function correctIndexFields(fields) {
6016 // Every index has to have deleted at the front, because when we do a query
6017 // we need to filter out deleted documents.
6018 return ['deleted'].concat(
6019 fields.map(function (field) {
6020 if (field in ['_id', '_rev', '_deleted', '_attachments']) {
6021 // These properties are stored at the top level without the underscore
6022 return field.substr(1);
6023 } else {
6024 // The custom document fields are inside the `data` property
6025 return 'data.' + sanitise(field, true);
6026 }
6027 })
6028 );
6029}
6030
6031//
6032// Core PouchDB schema version. Increment this if we, as a library, want to make
6033// schema changes in indexeddb. See upgradePouchDbSchema()
6034//
6035var POUCHDB_IDB_VERSION = 1;
6036
6037//
6038// Functions that manage a combinate indexeddb version, by combining the current
6039// time in millis that represents user migrations with a large multiplier that
6040// represents PouchDB system migrations.
6041//
6042// This lets us use the idb version number to both represent
6043// PouchDB-library-level migrations as well as "user migrations" required for
6044// when design documents trigger the addition or removal of native indexes.
6045//
6046// Given that Number.MAX_SAFE_INTEGER = 9007199254740991
6047//
6048// We can easily use the largest 2-3 digits and either allow:
6049// - 900 system migrations up to 2198/02/18
6050// - or 89 system migrations up to 5050/02/14
6051//
6052// This impl does the former. If this code still exists after 2198 someone send my
6053// decendents a Spacebook message congratulating them on their impressive genes.
6054//
6055// 9007199254740991 <- MAX_SAFE_INTEGER
6056// 10000000000000 <- 10^13
6057// 7199254740991 <- 2198-02-18T16:59:00.991Z
6058//
6059var versionMultiplier = Math.pow(10, 13);
6060function createIdbVersion() {
6061 return (versionMultiplier * POUCHDB_IDB_VERSION) + new Date().getTime();
6062}
6063function getPouchDbVersion(version) {
6064 return Math.floor(version / versionMultiplier);
6065}
6066
6067function maintainNativeIndexes(openReq, reject) {
6068 var docStore = openReq.transaction.objectStore(DOC_STORE);
6069 var ddocsReq = docStore.getAll(IDBKeyRange.bound('_design/', '_design/\uffff'));
6070
6071 ddocsReq.onsuccess = function (e) {
6072 var results = e.target.result;
6073 var existingIndexNames = Array.from(docStore.indexNames);
6074
6075 // NB: the only thing we're supporting here is the declared indexing
6076 // fields nothing more.
6077 var expectedIndexes = results.filter(function (row) {
6078 return row.deleted === 0 && row.revs[row.rev].data.views;
6079 }).map(function (row) {
6080 return row.revs[row.rev].data;
6081 }).reduce(function (indexes, ddoc) {
6082 return Object.keys(ddoc.views).reduce(function (acc, viewName) {
6083 var fields = rawIndexFields(ddoc, viewName);
6084
6085 if (fields && fields.length > 0) {
6086 acc[naturalIndexName(fields)] = correctIndexFields(fields);
6087 }
6088
6089 return acc;
6090 }, indexes);
6091 }, {});
6092
6093 var expectedIndexNames = Object.keys(expectedIndexes);
6094
6095 // Delete any indexes that aren't system indexes or expected
6096 var systemIndexNames = ['seq'];
6097 existingIndexNames.forEach(function (index) {
6098 if (systemIndexNames.indexOf(index) === -1 && expectedIndexNames.indexOf(index) === -1) {
6099 docStore.deleteIndex(index);
6100 }
6101 });
6102
6103 // Work out which indexes are missing and create them
6104 var newIndexNames = expectedIndexNames.filter(function (ei) {
6105 return existingIndexNames.indexOf(ei) === -1;
6106 });
6107
6108 try {
6109 newIndexNames.forEach(function (indexName) {
6110 docStore.createIndex(indexName, expectedIndexes[indexName]);
6111 });
6112 } catch (err) {
6113 reject(err);
6114 }
6115 };
6116}
6117
6118function upgradePouchDbSchema(db, pouchdbVersion) {
6119 if (pouchdbVersion < 1) {
6120 var docStore = db.createObjectStore(DOC_STORE, {keyPath : 'id'});
6121 docStore.createIndex('seq', 'seq', {unique: true});
6122
6123 db.createObjectStore(META_STORE, {keyPath: 'id'});
6124 }
6125
6126 // Declare more PouchDB schema changes here
6127 // if (pouchdbVersion < 2) { .. }
6128}
6129
6130function openDatabase(openDatabases, api, opts, resolve, reject) {
6131 var openReq = opts.versionchanged ?
6132 indexedDB.open(opts.name) :
6133 indexedDB.open(opts.name, createIdbVersion());
6134
6135 openReq.onupgradeneeded = function (e) {
6136 var db = e.target.result;
6137
6138 var pouchdbVersion = getPouchDbVersion(e.oldVersion);
6139 upgradePouchDbSchema(db, pouchdbVersion);
6140 maintainNativeIndexes(openReq, reject);
6141 };
6142
6143 openReq.onblocked = function (e) {
6144 // AFAICT this only occurs if, after sending `onversionchange` events to
6145 // all other open DBs (ie in different tabs), there are still open
6146 // connections to the DB. In this code we should never see this because we
6147 // close our DBs on these events, and all DB interactions are wrapped in
6148 // safely re-opening the DB.
6149 console.error('onblocked, this should never happen', e);
6150 };
6151
6152 openReq.onsuccess = function (e) {
6153 var idb = e.target.result;
6154
6155 idb.onabort = function (e) {
6156 console.error('Database has a global failure', e.target.error);
6157 delete openDatabases[opts.name];
6158 idb.close();
6159 };
6160
6161 idb.onversionchange = function () {
6162 console.log('Database was made stale, closing handle');
6163 openDatabases[opts.name].versionchanged = true;
6164 idb.close();
6165 };
6166
6167 var metadata = {id: META_STORE};
6168 var txn = idb.transaction([META_STORE], 'readwrite');
6169
6170 txn.oncomplete = function () {
6171 resolve({idb: idb, metadata: metadata});
6172 };
6173
6174 var metaStore = txn.objectStore(META_STORE);
6175 metaStore.get(META_STORE).onsuccess = function (e) {
6176 metadata = e.target.result || metadata;
6177 var changed = false;
6178
6179 if (!('doc_count' in metadata)) {
6180 changed = true;
6181 metadata.doc_count = 0;
6182 }
6183
6184 if (!('seq' in metadata)) {
6185 changed = true;
6186 metadata.seq = 0;
6187 }
6188
6189 if (!('db_uuid' in metadata)) {
6190 changed = true;
6191 metadata.db_uuid = uuid();
6192 }
6193
6194 if (changed) {
6195 metaStore.put(metadata);
6196 }
6197 };
6198 };
6199}
6200
6201function setup (openDatabases, api, opts) {
6202 if (!openDatabases[opts.name] || openDatabases[opts.name].versionchanged) {
6203 opts.versionchanged = openDatabases[opts.name] &&
6204 openDatabases[opts.name].versionchanged;
6205
6206 openDatabases[opts.name] = new Promise(function (resolve, reject) {
6207 openDatabase(openDatabases, api, opts, resolve, reject);
6208 });
6209 }
6210
6211 return openDatabases[opts.name];
6212}
6213
6214function info (metadata, callback) {
6215 callback(null, {
6216 doc_count: metadata.doc_count,
6217 update_seq: metadata.seq
6218 });
6219}
6220
6221function get (txn, id, opts, callback) {
6222 if (txn.error) {
6223 return callback(txn.error);
6224 }
6225
6226 txn.txn.objectStore(DOC_STORE).get(id).onsuccess = function (e) {
6227 var doc = e.target.result;
6228 var rev;
6229 if (!opts.rev) {
6230 rev = (doc && doc.rev);
6231 } else {
6232 rev = opts.latest ? latest(opts.rev, doc) : opts.rev;
6233 }
6234
6235 if (!doc || (doc.deleted && !opts.rev) || !(rev in doc.revs)) {
6236 callback(createError(MISSING_DOC, 'missing'));
6237 return;
6238 }
6239
6240 var result = doc.revs[rev].data;
6241 result._id = doc.id;
6242 result._rev = rev;
6243
6244 // WARNING: expecting possible old format
6245 // TODO: why are we passing the transaction in the context?
6246 // It's not clear we ever thread these txns usefully
6247 callback(null, {
6248 doc: result,
6249 metadata: doc,
6250 ctx: txn
6251 });
6252 };
6253}
6254
6255function parseAttachment(attachment, opts, cb) {
6256 if (opts.binary) {
6257 return cb(null, attachment);
6258 } else {
6259 readAsBinaryString(attachment, function (binString) {
6260 cb(null, thisBtoa(binString));
6261 });
6262 }
6263}
6264
6265function getAttachment(txn, docId, attachId, _, opts, cb) {
6266 if (txn.error) {
6267 return cb(txn.error);
6268 }
6269
6270 var attachment;
6271
6272 txn.txn.objectStore(DOC_STORE).get(docId).onsuccess = function (e) {
6273 var doc = e.target.result;
6274 var rev = doc.revs[opts.rev || doc.rev].data;
6275 var digest = rev._attachments[attachId].digest;
6276 attachment = doc.attachments[digest].data;
6277 };
6278
6279 txn.txn.oncomplete = function () {
6280 parseAttachment(attachment, opts, cb);
6281 };
6282
6283 txn.txn.onabort = cb;
6284}
6285
6286function toObject(array) {
6287 return array.reduce(function (obj, item) {
6288 obj[item] = true;
6289 return obj;
6290 }, {});
6291}
6292// List of top level reserved words for doc
6293var reservedWords = toObject([
6294 '_id',
6295 '_rev',
6296 '_attachments',
6297 '_deleted',
6298 '_revisions',
6299 '_revs_info',
6300 '_conflicts',
6301 '_deleted_conflicts',
6302 '_local_seq',
6303 '_rev_tree',
6304 //replication documents
6305 '_replication_id',
6306 '_replication_state',
6307 '_replication_state_time',
6308 '_replication_state_reason',
6309 '_replication_stats',
6310 // Specific to Couchbase Sync Gateway
6311 '_removed'
6312]);
6313
6314// List of reserved words that should end up the document
6315var dataWords = toObject([
6316 '_attachments',
6317 //replication documents
6318 '_replication_id',
6319 '_replication_state',
6320 '_replication_state_time',
6321 '_replication_state_reason',
6322 '_replication_stats'
6323]);
6324
6325function parseRevisionInfo(rev$$1) {
6326 if (!/^\d+-./.test(rev$$1)) {
6327 return createError(INVALID_REV);
6328 }
6329 var idx = rev$$1.indexOf('-');
6330 var left = rev$$1.substring(0, idx);
6331 var right = rev$$1.substring(idx + 1);
6332 return {
6333 prefix: parseInt(left, 10),
6334 id: right
6335 };
6336}
6337
6338function makeRevTreeFromRevisions(revisions, opts) {
6339 var pos = revisions.start - revisions.ids.length + 1;
6340
6341 var revisionIds = revisions.ids;
6342 var ids = [revisionIds[0], opts, []];
6343
6344 for (var i = 1, len = revisionIds.length; i < len; i++) {
6345 ids = [revisionIds[i], {status: 'missing'}, [ids]];
6346 }
6347
6348 return [{
6349 pos: pos,
6350 ids: ids
6351 }];
6352}
6353
6354// Preprocess documents, parse their revisions, assign an id and a
6355// revision for new writes that are missing them, etc
6356function parseDoc(doc, newEdits, dbOpts) {
6357 if (!dbOpts) {
6358 dbOpts = {
6359 deterministic_revs: true
6360 };
6361 }
6362
6363 var nRevNum;
6364 var newRevId;
6365 var revInfo;
6366 var opts = {status: 'available'};
6367 if (doc._deleted) {
6368 opts.deleted = true;
6369 }
6370
6371 if (newEdits) {
6372 if (!doc._id) {
6373 doc._id = uuid();
6374 }
6375 newRevId = rev(doc, dbOpts.deterministic_revs);
6376 if (doc._rev) {
6377 revInfo = parseRevisionInfo(doc._rev);
6378 if (revInfo.error) {
6379 return revInfo;
6380 }
6381 doc._rev_tree = [{
6382 pos: revInfo.prefix,
6383 ids: [revInfo.id, {status: 'missing'}, [[newRevId, opts, []]]]
6384 }];
6385 nRevNum = revInfo.prefix + 1;
6386 } else {
6387 doc._rev_tree = [{
6388 pos: 1,
6389 ids : [newRevId, opts, []]
6390 }];
6391 nRevNum = 1;
6392 }
6393 } else {
6394 if (doc._revisions) {
6395 doc._rev_tree = makeRevTreeFromRevisions(doc._revisions, opts);
6396 nRevNum = doc._revisions.start;
6397 newRevId = doc._revisions.ids[0];
6398 }
6399 if (!doc._rev_tree) {
6400 revInfo = parseRevisionInfo(doc._rev);
6401 if (revInfo.error) {
6402 return revInfo;
6403 }
6404 nRevNum = revInfo.prefix;
6405 newRevId = revInfo.id;
6406 doc._rev_tree = [{
6407 pos: nRevNum,
6408 ids: [newRevId, opts, []]
6409 }];
6410 }
6411 }
6412
6413 invalidIdError(doc._id);
6414
6415 doc._rev = nRevNum + '-' + newRevId;
6416
6417 var result = {metadata : {}, data : {}};
6418 for (var key in doc) {
6419 /* istanbul ignore else */
6420 if (Object.prototype.hasOwnProperty.call(doc, key)) {
6421 var specialKey = key[0] === '_';
6422 if (specialKey && !reservedWords[key]) {
6423 var error = createError(DOC_VALIDATION, key);
6424 error.message = DOC_VALIDATION.message + ': ' + key;
6425 throw error;
6426 } else if (specialKey && !dataWords[key]) {
6427 result.metadata[key.slice(1)] = doc[key];
6428 } else {
6429 result.data[key] = doc[key];
6430 }
6431 }
6432 }
6433 return result;
6434}
6435
6436function bulkDocs (api, req, opts, metadata, dbOpts, idbChanges, callback) {
6437
6438 var txn;
6439
6440 // TODO: I would prefer to get rid of these globals
6441 var error;
6442 var results = [];
6443 var docs = [];
6444 var lastWriteIndex;
6445
6446 var revsLimit = dbOpts.revs_limit || 1000;
6447 var rewriteEnabled = dbOpts.name.indexOf("-mrview-") === -1;
6448
6449 // We only need to track 1 revision for local documents
6450 function docsRevsLimit(doc) {
6451 return /^_local/.test(doc.id) ? 1 : revsLimit;
6452 }
6453
6454 function rootIsMissing(doc) {
6455 return doc.rev_tree[0].ids[1].status === 'missing';
6456 }
6457
6458 function parseBase64(data) {
6459 try {
6460 return atob(data);
6461 } catch (e) {
6462 return {
6463 error: createError(BAD_ARG, 'Attachment is not a valid base64 string')
6464 };
6465 }
6466 }
6467
6468 // Reads the original doc from the store if available
6469 // As in allDocs with keys option using multiple get calls is the fastest way
6470 function fetchExistingDocs(txn, docs) {
6471 var fetched = 0;
6472 var oldDocs = {};
6473
6474 function readDone(e) {
6475 if (e.target.result) {
6476 oldDocs[e.target.result.id] = e.target.result;
6477 }
6478 if (++fetched === docs.length) {
6479 processDocs$$1(txn, docs, oldDocs);
6480 }
6481 }
6482
6483 docs.forEach(function (doc) {
6484 txn.objectStore(DOC_STORE).get(doc.id).onsuccess = readDone;
6485 });
6486 }
6487
6488 function processDocs$$1(txn, docs, oldDocs) {
6489
6490 docs.forEach(function (doc, i) {
6491 var newDoc;
6492
6493 // The first document write cannot be a deletion
6494 if ('was_delete' in opts && !(oldDocs.hasOwnProperty(doc.id))) {
6495 newDoc = createError(MISSING_DOC, 'deleted');
6496
6497 // The first write of a document cannot specify a revision
6498 } else if (opts.new_edits &&
6499 !oldDocs.hasOwnProperty(doc.id) &&
6500 rootIsMissing(doc)) {
6501 newDoc = createError(REV_CONFLICT);
6502
6503 // Update the existing document
6504 } else if (oldDocs.hasOwnProperty(doc.id)) {
6505 newDoc = update(txn, doc, oldDocs[doc.id]);
6506 // The update can be rejected if it is an update to an existing
6507 // revision, if so skip it
6508 if (newDoc == false) {
6509 return;
6510 }
6511
6512 // New document
6513 } else {
6514 // Ensure new documents are also stemmed
6515 var merged = merge([], doc.rev_tree[0], docsRevsLimit(doc));
6516 doc.rev_tree = merged.tree;
6517 doc.stemmedRevs = merged.stemmedRevs;
6518 newDoc = doc;
6519 newDoc.isNewDoc = true;
6520 newDoc.wasDeleted = doc.revs[doc.rev].deleted ? 1 : 0;
6521 }
6522
6523 if (newDoc.error) {
6524 results[i] = newDoc;
6525 } else {
6526 oldDocs[newDoc.id] = newDoc;
6527 lastWriteIndex = i;
6528 write(txn, newDoc, i);
6529 }
6530 });
6531 }
6532
6533 // Converts from the format returned by parseDoc into the new format
6534 // we use to store
6535 function convertDocFormat(doc) {
6536
6537 var newDoc = {
6538 id: doc.metadata.id,
6539 rev: doc.metadata.rev,
6540 rev_tree: doc.metadata.rev_tree,
6541 revs: doc.metadata.revs || {}
6542 };
6543
6544 newDoc.revs[newDoc.rev] = {
6545 data: doc.data,
6546 deleted: doc.metadata.deleted
6547 };
6548
6549 return newDoc;
6550 }
6551
6552 function update(txn, doc, oldDoc) {
6553
6554 // Ignore updates to existing revisions
6555 if ((doc.rev in oldDoc.revs) && !opts.new_edits) {
6556 return false;
6557 }
6558
6559 var isRoot = /^1-/.test(doc.rev);
6560
6561 // Reattach first writes after a deletion to last deleted tree
6562 if (oldDoc.deleted && !doc.deleted && opts.new_edits && isRoot) {
6563 var tmp = doc.revs[doc.rev].data;
6564 tmp._rev = oldDoc.rev;
6565 tmp._id = oldDoc.id;
6566 doc = convertDocFormat(parseDoc(tmp, opts.new_edits, dbOpts));
6567 }
6568
6569 var merged = merge(oldDoc.rev_tree, doc.rev_tree[0], docsRevsLimit(doc));
6570 doc.stemmedRevs = merged.stemmedRevs;
6571 doc.rev_tree = merged.tree;
6572
6573 // Merge the old and new rev data
6574 var revs = oldDoc.revs;
6575 revs[doc.rev] = doc.revs[doc.rev];
6576 doc.revs = revs;
6577
6578 doc.attachments = oldDoc.attachments;
6579
6580 var inConflict = opts.new_edits && (((oldDoc.deleted && doc.deleted) ||
6581 (!oldDoc.deleted && merged.conflicts !== 'new_leaf') ||
6582 (oldDoc.deleted && !doc.deleted && merged.conflicts === 'new_branch') ||
6583 (oldDoc.rev === doc.rev)));
6584
6585 if (inConflict) {
6586 return createError(REV_CONFLICT);
6587 }
6588
6589 doc.wasDeleted = oldDoc.deleted;
6590
6591 return doc;
6592 }
6593
6594 function write(txn, doc, i) {
6595
6596 // We copy the data from the winning revision into the root
6597 // of the document so that it can be indexed
6598 var winningRev$$1 = winningRev(doc);
6599 // rev of new doc for attachments and to return it
6600 var writtenRev = doc.rev;
6601 var isLocal = /^_local/.test(doc.id);
6602
6603 var theDoc = doc.revs[winningRev$$1].data;
6604
6605 if (rewriteEnabled) {
6606 // doc.data is what we index, so we need to clone and rewrite it, and clean
6607 // it up for indexability
6608 var result = rewrite(theDoc);
6609 if (result) {
6610 doc.data = result;
6611 delete doc.data._attachments;
6612 } else {
6613 doc.data = theDoc;
6614 }
6615 } else {
6616 doc.data = theDoc;
6617 }
6618
6619 doc.rev = winningRev$$1;
6620 // .deleted needs to be an int for indexing
6621 doc.deleted = doc.revs[winningRev$$1].deleted ? 1 : 0;
6622
6623 // Bump the seq for every new (non local) revision written
6624 // TODO: index expects a unique seq, not sure if ignoring local will
6625 // work
6626 if (!isLocal) {
6627 doc.seq = ++metadata.seq;
6628
6629 var delta = 0;
6630 // If its a new document, we wont decrement if deleted
6631 if (doc.isNewDoc) {
6632 delta = doc.deleted ? 0 : 1;
6633 } else if (doc.wasDeleted !== doc.deleted) {
6634 delta = doc.deleted ? -1 : 1;
6635 }
6636 metadata.doc_count += delta;
6637 }
6638 delete doc.isNewDoc;
6639 delete doc.wasDeleted;
6640
6641 // If there have been revisions stemmed when merging trees,
6642 // delete their data
6643 if (doc.stemmedRevs) {
6644 doc.stemmedRevs.forEach(function (rev) { delete doc.revs[rev]; });
6645 }
6646 delete doc.stemmedRevs;
6647
6648 if (!('attachments' in doc)) {
6649 doc.attachments = {};
6650 }
6651
6652 if (theDoc._attachments) {
6653 for (var k in theDoc._attachments) {
6654 var attachment = theDoc._attachments[k];
6655 if (attachment.stub) {
6656 if (!(attachment.digest in doc.attachments)) {
6657 error = createError(MISSING_STUB);
6658 // TODO: Not sure how safe this manual abort is, seeing
6659 // console issues
6660 txn.abort();
6661 return;
6662 }
6663
6664 doc.attachments[attachment.digest].revs[writtenRev] = true;
6665
6666 } else {
6667
6668 doc.attachments[attachment.digest] = attachment;
6669 doc.attachments[attachment.digest].revs = {};
6670 doc.attachments[attachment.digest].revs[writtenRev] = true;
6671
6672 theDoc._attachments[k] = {
6673 stub: true,
6674 digest: attachment.digest,
6675 content_type: attachment.content_type,
6676 length: attachment.length,
6677 revpos: parseInt(writtenRev, 10)
6678 };
6679 }
6680 }
6681 }
6682
6683 // Local documents have different revision handling
6684 if (isLocal && doc.deleted) {
6685 txn.objectStore(DOC_STORE)["delete"](doc.id).onsuccess = function () {
6686 results[i] = {
6687 ok: true,
6688 id: doc.id,
6689 rev: '0-0'
6690 };
6691 };
6692 updateSeq(i);
6693 return;
6694 }
6695
6696 txn.objectStore(DOC_STORE).put(doc).onsuccess = function () {
6697 results[i] = {
6698 ok: true,
6699 id: doc.id,
6700 rev: writtenRev
6701 };
6702 updateSeq(i);
6703 };
6704 }
6705
6706 function updateSeq(i) {
6707 if (i === lastWriteIndex) {
6708 txn.objectStore(META_STORE).put(metadata);
6709 }
6710 }
6711
6712 function preProcessAttachment(attachment) {
6713 if (attachment.stub) {
6714 return Promise.resolve(attachment);
6715 }
6716
6717 var binData;
6718 if (typeof attachment.data === 'string') {
6719 binData = parseBase64(attachment.data);
6720 if (binData.error) {
6721 return Promise.reject(binData.error);
6722 }
6723 attachment.data = binStringToBluffer(binData, attachment.content_type);
6724 } else {
6725 binData = attachment.data;
6726 }
6727
6728 return new Promise(function (resolve) {
6729 binaryMd5(binData, function (result) {
6730 attachment.digest = 'md5-' + result;
6731 attachment.length = binData.size || binData.length || 0;
6732 resolve(attachment);
6733 });
6734 });
6735 }
6736
6737 function preProcessAttachments() {
6738 var promises = docs.map(function (doc) {
6739 var data = doc.revs[doc.rev].data;
6740 if (!data._attachments) {
6741 return Promise.resolve(data);
6742 }
6743 var attachments = Object.keys(data._attachments).map(function (k) {
6744 data._attachments[k].name = k;
6745 return preProcessAttachment(data._attachments[k]);
6746 });
6747
6748 return Promise.all(attachments).then(function (newAttachments) {
6749 var processed = {};
6750 newAttachments.forEach(function (attachment) {
6751 processed[attachment.name] = attachment;
6752 delete attachment.name;
6753 });
6754 data._attachments = processed;
6755 return data;
6756 });
6757 });
6758 return Promise.all(promises);
6759 }
6760
6761 for (var i = 0, len = req.docs.length; i < len; i++) {
6762 var result;
6763 // TODO: We should get rid of throwing for invalid docs, also not sure
6764 // why this is needed in idb-next and not idb
6765 try {
6766 result = parseDoc(req.docs[i], opts.new_edits, dbOpts);
6767 } catch (err) {
6768 result = err;
6769 }
6770 if (result.error) {
6771 return callback(result);
6772 }
6773
6774 // Ideally parseDoc would return data in this format, but it is currently
6775 // shared so we need to convert
6776 docs.push(convertDocFormat(result));
6777 }
6778
6779 preProcessAttachments().then(function () {
6780 api._openTransactionSafely([DOC_STORE, META_STORE], 'readwrite', function (err, _txn) {
6781 if (err) {
6782 return callback(err);
6783 }
6784
6785 txn = _txn;
6786
6787 txn.onabort = function () {
6788 callback(error);
6789 };
6790 txn.ontimeout = idbError(callback);
6791
6792 txn.oncomplete = function () {
6793 idbChanges.notify(dbOpts.name);
6794 callback(null, results);
6795 };
6796
6797 // We would like to use promises here, but idb sucks
6798 fetchExistingDocs(txn, docs);
6799 });
6800 })["catch"](function (err) {
6801 callback(err);
6802 });
6803}
6804
6805function allDocsKeys(keys, docStore, allDocsInner) {
6806 // It's not guaranted to be returned in right order
6807 var valuesBatch = new Array(keys.length);
6808 var count = 0;
6809 keys.forEach(function (key, index) {
6810 docStore.get(key).onsuccess = function (event) {
6811 if (event.target.result) {
6812 valuesBatch[index] = event.target.result;
6813 } else {
6814 valuesBatch[index] = {key: key, error: 'not_found'};
6815 }
6816 count++;
6817 if (count === keys.length) {
6818 valuesBatch.forEach(function (doc) {
6819 allDocsInner(doc);
6820 });
6821 }
6822 };
6823 });
6824}
6825
6826function createKeyRange(start, end, inclusiveEnd, key, descending) {
6827 try {
6828 if (start && end) {
6829 if (descending) {
6830 return IDBKeyRange.bound(end, start, !inclusiveEnd, false);
6831 } else {
6832 return IDBKeyRange.bound(start, end, false, !inclusiveEnd);
6833 }
6834 } else if (start) {
6835 if (descending) {
6836 return IDBKeyRange.upperBound(start);
6837 } else {
6838 return IDBKeyRange.lowerBound(start);
6839 }
6840 } else if (end) {
6841 if (descending) {
6842 return IDBKeyRange.lowerBound(end, !inclusiveEnd);
6843 } else {
6844 return IDBKeyRange.upperBound(end, !inclusiveEnd);
6845 }
6846 } else if (key) {
6847 return IDBKeyRange.only(key);
6848 }
6849 } catch (e) {
6850 return {error: e};
6851 }
6852 return null;
6853}
6854
6855function handleKeyRangeError(opts, metadata, err, callback) {
6856 if (err.name === "DataError" && err.code === 0) {
6857 // data error, start is less than end
6858 var returnVal = {
6859 total_rows: metadata.doc_count,
6860 offset: opts.skip,
6861 rows: []
6862 };
6863 /* istanbul ignore if */
6864 if (opts.update_seq) {
6865 returnVal.update_seq = metadata.seq;
6866 }
6867 return callback(null, returnVal);
6868 }
6869 callback(createError(IDB_ERROR, err.name, err.message));
6870}
6871
6872function allDocs (txn, metadata, opts, callback) {
6873 if (txn.error) {
6874 return callback(txn.error);
6875 }
6876
6877 // TODO: Weird hack, I dont like it
6878 if (opts.limit === 0) {
6879 var returnVal = {
6880 total_rows: metadata.doc_count,
6881 offset: opts.skip,
6882 rows: []
6883 };
6884
6885 /* istanbul ignore if */
6886 if (opts.update_seq) {
6887 returnVal.update_seq = metadata.seq;
6888 }
6889 return callback(null, returnVal);
6890 }
6891
6892 var results = [];
6893 var processing = [];
6894
6895 var start = 'startkey' in opts ? opts.startkey : false;
6896 var end = 'endkey' in opts ? opts.endkey : false;
6897 var key = 'key' in opts ? opts.key : false;
6898 var keys = 'keys' in opts ? opts.keys : false;
6899 var skip = opts.skip || 0;
6900 var limit = typeof opts.limit === 'number' ? opts.limit : -1;
6901 var inclusiveEnd = opts.inclusive_end !== false;
6902 var descending = 'descending' in opts && opts.descending ? 'prev' : null;
6903
6904 var keyRange;
6905 if (!keys) {
6906 keyRange = createKeyRange(start, end, inclusiveEnd, key, descending);
6907 if (keyRange && keyRange.error) {
6908 return handleKeyRangeError(opts, metadata, keyRange.error, callback);
6909 }
6910 }
6911
6912 var docStore = txn.txn.objectStore(DOC_STORE);
6913
6914 txn.txn.oncomplete = onTxnComplete;
6915
6916 if (keys) {
6917 return allDocsKeys(opts.keys, docStore, allDocsInner);
6918 }
6919
6920 function include_doc(row, doc) {
6921 var docData = doc.revs[doc.rev].data;
6922
6923 row.doc = docData;
6924 row.doc._id = doc.id;
6925 row.doc._rev = doc.rev;
6926 if (opts.conflicts) {
6927 var conflicts = collectConflicts(doc);
6928 if (conflicts.length) {
6929 row.doc._conflicts = conflicts;
6930 }
6931 }
6932 if (opts.attachments && docData._attachments) {
6933 for (var name in docData._attachments) {
6934 processing.push(processAttachment(name, doc, row.doc, opts.binary));
6935 }
6936 }
6937 }
6938
6939 function allDocsInner(doc) {
6940 if (doc.error && keys) {
6941 // key was not found with "keys" requests
6942 results.push(doc);
6943 return true;
6944 }
6945
6946 var row = {
6947 id: doc.id,
6948 key: doc.id,
6949 value: {
6950 rev: doc.rev
6951 }
6952 };
6953
6954 var deleted = doc.deleted;
6955 if (deleted) {
6956 if (keys) {
6957 results.push(row);
6958 row.value.deleted = true;
6959 row.doc = null;
6960 }
6961 } else if (skip-- <= 0) {
6962 results.push(row);
6963 if (opts.include_docs) {
6964 include_doc(row, doc);
6965 }
6966 if (--limit === 0) {
6967 return false;
6968 }
6969 }
6970 return true;
6971 }
6972
6973 function onTxnComplete() {
6974 Promise.all(processing).then(function () {
6975 var returnVal = {
6976 total_rows: metadata.doc_count,
6977 offset: 0,
6978 rows: results
6979 };
6980
6981 /* istanbul ignore if */
6982 if (opts.update_seq) {
6983 returnVal.update_seq = metadata.seq;
6984 }
6985 callback(null, returnVal);
6986 });
6987 }
6988
6989 var cursor = descending ?
6990 docStore.openCursor(keyRange, descending) :
6991 docStore.openCursor(keyRange);
6992
6993 cursor.onsuccess = function (e) {
6994
6995 var doc = e.target.result && e.target.result.value;
6996
6997 // Happens if opts does not have limit,
6998 // because cursor will end normally then,
6999 // when all docs are retrieved.
7000 // Would not be needed, if getAll() optimization was used like in #6059
7001 if (!doc) { return; }
7002
7003 // Skip local docs
7004 if (/^_local/.test(doc.id)) {
7005 return e.target.result["continue"]();
7006 }
7007
7008 var continueCursor = allDocsInner(doc);
7009 if (continueCursor) {
7010 e.target.result["continue"]();
7011 }
7012 };
7013
7014}
7015
7016function changes (txn, idbChanges, api, dbOpts, opts) {
7017 if (txn.error) {
7018 return opts.complete(txn.error);
7019 }
7020
7021 if (opts.continuous) {
7022 var id = dbOpts.name + ':' + uuid();
7023 idbChanges.addListener(dbOpts.name, id, api, opts);
7024 idbChanges.notify(dbOpts.name);
7025 return {
7026 cancel: function () {
7027 idbChanges.removeListener(dbOpts.name, id);
7028 }
7029 };
7030 }
7031
7032 var limit = 'limit' in opts ? opts.limit : -1;
7033 if (limit === 0) {
7034 limit = 1;
7035 }
7036
7037 var store = txn.txn.objectStore(DOC_STORE).index('seq');
7038
7039 var filter = filterChange(opts);
7040 var received = 0;
7041
7042 var lastSeq = opts.since || 0;
7043 var results = [];
7044
7045 var processing = [];
7046
7047 function onReqSuccess(e) {
7048 if (!e.target.result) { return; }
7049 var cursor = e.target.result;
7050 var doc = cursor.value;
7051 // Overwrite doc.data, which may have been rewritten (see rewrite.js) with
7052 // the clean version for that rev
7053 doc.data = doc.revs[doc.rev].data;
7054 doc.data._id = doc.id;
7055 doc.data._rev = doc.rev;
7056 if (doc.deleted) {
7057 doc.data._deleted = true;
7058 }
7059
7060 if (opts.doc_ids && opts.doc_ids.indexOf(doc.id) === -1) {
7061 return cursor["continue"]();
7062 }
7063
7064 // WARNING: expecting possible old format
7065 var change = opts.processChange(doc.data, doc, opts);
7066 change.seq = doc.seq;
7067 lastSeq = doc.seq;
7068 var filtered = filter(change);
7069
7070 // If its an error
7071 if (typeof filtered === 'object') {
7072 return opts.complete(filtered);
7073 }
7074
7075 if (filtered) {
7076 received++;
7077 if (opts.return_docs) {
7078 results.push(change);
7079 }
7080
7081 if (opts.include_docs && opts.attachments && doc.data._attachments) {
7082 var promises = [];
7083 for (var name in doc.data._attachments) {
7084 var p = processAttachment(name, doc, change.doc, opts.binary);
7085 // We add the processing promise to 2 arrays, one tracks all
7086 // the promises needed before we fire onChange, the other
7087 // ensure we process all attachments before onComplete
7088 promises.push(p);
7089 processing.push(p);
7090 }
7091
7092 Promise.all(promises).then(function () {
7093 opts.onChange(change);
7094 });
7095 } else {
7096 opts.onChange(change);
7097 }
7098 }
7099 if (received !== limit) {
7100 cursor["continue"]();
7101 }
7102 }
7103
7104 function onTxnComplete() {
7105 Promise.all(processing).then(function () {
7106 opts.complete(null, {
7107 results: results,
7108 last_seq: lastSeq
7109 });
7110 });
7111 }
7112
7113 var req;
7114 if (opts.descending) {
7115 req = store.openCursor(null, 'prev');
7116 } else {
7117 req = store.openCursor(IDBKeyRange.lowerBound(opts.since, true));
7118 }
7119
7120 txn.txn.oncomplete = onTxnComplete;
7121 req.onsuccess = onReqSuccess;
7122}
7123
7124function getRevisionTree (txn, id, callback) {
7125 if (txn.error) {
7126 return callback(txn.error);
7127 }
7128
7129 var req = txn.txn.objectStore(DOC_STORE).get(id);
7130 req.onsuccess = function (e) {
7131 if (!e.target.result) {
7132 callback(createError(MISSING_DOC));
7133 } else {
7134 callback(null, e.target.result.rev_tree);
7135 }
7136 };
7137}
7138
7139function doCompaction (txn, id, revs, callback) {
7140 if (txn.error) {
7141 return callback(txn.error);
7142 }
7143
7144 var docStore = txn.txn.objectStore(DOC_STORE);
7145
7146 docStore.get(id).onsuccess = function (e) {
7147 var doc = e.target.result;
7148
7149 traverseRevTree(doc.rev_tree, function (isLeaf, pos, revHash, ctx, opts) {
7150 var rev = pos + '-' + revHash;
7151 if (revs.indexOf(rev) !== -1) {
7152 opts.status = 'missing';
7153 }
7154 });
7155
7156 var attachments = [];
7157
7158 revs.forEach(function (rev) {
7159 if (rev in doc.revs) {
7160 // Make a list of attachments that are used by the revisions being
7161 // deleted
7162 if (doc.revs[rev].data._attachments) {
7163 for (var k in doc.revs[rev].data._attachments) {
7164 attachments.push(doc.revs[rev].data._attachments[k].digest);
7165 }
7166 }
7167 delete doc.revs[rev];
7168 }
7169 });
7170
7171 // Attachments have a list of revisions that are using them, when
7172 // that list becomes empty we can delete the attachment.
7173 attachments.forEach(function (digest) {
7174 revs.forEach(function (rev) {
7175 delete doc.attachments[digest].revs[rev];
7176 });
7177 if (!Object.keys(doc.attachments[digest].revs).length) {
7178 delete doc.attachments[digest];
7179 }
7180 });
7181
7182 docStore.put(doc);
7183 };
7184
7185 txn.txn.oncomplete = function () {
7186 callback();
7187 };
7188}
7189
7190function destroy (dbOpts, openDatabases, idbChanges, callback) {
7191
7192 idbChanges.removeAllListeners(dbOpts.name);
7193
7194 function doDestroy() {
7195 var req = indexedDB.deleteDatabase(dbOpts.name);
7196 req.onsuccess = function () {
7197 delete openDatabases[dbOpts.name];
7198 callback(null, {ok: true});
7199 };
7200 }
7201
7202 // If the database is open we need to close it
7203 if (dbOpts.name in openDatabases) {
7204 openDatabases[dbOpts.name].then(function (res) {
7205 res.idb.close();
7206 doDestroy();
7207 });
7208 } else {
7209 doDestroy();
7210 }
7211
7212}
7213
7214// Adapted from
7215// https://github.com/pouchdb/pouchdb/blob/master/packages/node_modules/pouchdb-find/src/adapters/local/find/query-planner.js#L20-L24
7216// This could change / improve in the future?
7217var COUCH_COLLATE_LO = null;
7218var COUCH_COLLATE_HI = '\uffff'; // actually used as {"\uffff": {}}
7219
7220// Adapted from: https://www.w3.org/TR/IndexedDB/#compare-two-keys
7221// Importantly, *there is no upper bound possible* in idb. The ideal data
7222// structure an infintely deep array:
7223// var IDB_COLLATE_HI = []; IDB_COLLATE_HI.push(IDB_COLLATE_HI)
7224// But IDBKeyRange is not a fan of shenanigans, so I've just gone with 12 layers
7225// because it looks nice and surely that's enough!
7226var IDB_COLLATE_LO = Number.NEGATIVE_INFINITY;
7227var IDB_COLLATE_HI = [[[[[[[[[[[[]]]]]]]]]]]];
7228
7229//
7230// TODO: this should be made offical somewhere and used by AllDocs / get /
7231// changes etc as well.
7232//
7233function externaliseRecord(idbDoc) {
7234 var doc = idbDoc.revs[idbDoc.rev].data;
7235 doc._id = idbDoc.id;
7236 doc._rev = idbDoc.rev;
7237 if (idbDoc.deleted) {
7238 doc._deleted = true;
7239 }
7240
7241 return doc;
7242}
7243
7244/**
7245 * Generates a keyrange based on the opts passed to query
7246 *
7247 * The first key is always 0, as that's how we're filtering out deleted entries.
7248 */
7249function generateKeyRange(opts) {
7250 function defined(obj, k) {
7251 return obj[k] !== void 0;
7252 }
7253
7254 // Converts a valid CouchDB key into a valid IndexedDB one
7255 function convert(key, exact) {
7256 // The first item in every native index is doc.deleted, and we always want
7257 // to only search documents that are not deleted.
7258 // "foo" -> [0, "foo"]
7259 var filterDeleted = [0].concat(key);
7260
7261 return filterDeleted.map(function (k) {
7262 // null, true and false are not indexable by indexeddb. When we write
7263 // these values we convert them to these constants, and so when we
7264 // query for them we need to convert the query also.
7265 if (k === null && exact) {
7266 // for non-exact queries we treat null as a collate property
7267 // see `if (!exact)` block below
7268 return IDB_NULL;
7269 } else if (k === true) {
7270 return IDB_TRUE;
7271 } else if (k === false) {
7272 return IDB_FALSE;
7273 }
7274
7275 if (!exact) {
7276 // We get passed CouchDB's collate low and high values, so for non-exact
7277 // ranged queries we're going to convert them to our IDB equivalents
7278 if (k === COUCH_COLLATE_LO) {
7279 return IDB_COLLATE_LO;
7280 } else if (k.hasOwnProperty(COUCH_COLLATE_HI)) {
7281 return IDB_COLLATE_HI;
7282 }
7283 }
7284
7285 return k;
7286 });
7287 }
7288
7289 // CouchDB and so PouchdB defaults to true. We need to make this explicit as
7290 // we invert these later for IndexedDB.
7291 if (!defined(opts, 'inclusive_end')) {
7292 opts.inclusive_end = true;
7293 }
7294 if (!defined(opts, 'inclusive_start')) {
7295 opts.inclusive_start = true;
7296 }
7297
7298 if (opts.descending) {
7299 // Flip before generating. We'll check descending again later when performing
7300 // an index request
7301 var realEndkey = opts.startkey,
7302 realInclusiveEnd = opts.inclusive_start;
7303
7304 opts.startkey = opts.endkey;
7305 opts.endkey = realEndkey;
7306 opts.inclusive_start = opts.inclusive_end;
7307 opts.inclusive_end = realInclusiveEnd;
7308 }
7309
7310 try {
7311 if (defined(opts, 'key')) {
7312 return IDBKeyRange.only(convert(opts.key, true));
7313 }
7314
7315 if (defined(opts, 'startkey') && !defined(opts, 'endkey')) {
7316 return IDBKeyRange.lowerBound(convert(opts.startkey), !opts.inclusive_start);
7317 }
7318
7319 if (!defined(opts, 'startkey') && defined(opts, 'endkey')) {
7320 return IDBKeyRange.upperBound(convert(opts.endkey), !opts.inclusive_end);
7321 }
7322
7323 if (defined(opts, 'startkey') && defined(opts, 'endkey')) {
7324 return IDBKeyRange.bound(
7325 convert(opts.startkey), convert(opts.endkey),
7326 !opts.inclusive_start, !opts.inclusive_end
7327 );
7328 }
7329
7330 return IDBKeyRange.only([0]);
7331 } catch (err) {
7332 console.error('Could not generate keyRange', err, opts);
7333 throw Error('Could not generate key range with ' + JSON.stringify(opts));
7334 }
7335}
7336
7337function getIndexHandle(pdb, fields, reject) {
7338 var indexName = naturalIndexName(fields);
7339
7340 return new Promise(function (resolve) {
7341 pdb._openTransactionSafely([DOC_STORE], 'readonly', function (err, txn) {
7342 if (err) {
7343 return idbError(reject)(err);
7344 }
7345
7346 txn.onabort = idbError(reject);
7347 txn.ontimeout = idbError(reject);
7348
7349 var existingIndexNames = Array.from(txn.objectStore(DOC_STORE).indexNames);
7350
7351 if (existingIndexNames.indexOf(indexName) === -1) {
7352 // The index is missing, force a db restart and try again
7353 pdb._freshen()
7354 .then(function () { return getIndexHandle(pdb, fields, reject); })
7355 .then(resolve);
7356 } else {
7357 resolve(txn.objectStore(DOC_STORE).index(indexName));
7358 }
7359 });
7360 });
7361}
7362
7363// In theory we should return something like the doc example below, but find
7364// only needs rows: [{doc: {...}}], so I think we can just not bother for now
7365// {
7366// "offset" : 0,
7367// "rows": [{
7368// "id": "doc3",
7369// "key": "Lisa Says",
7370// "value": null,
7371// "doc": {
7372// "_id": "doc3",
7373// "_rev": "1-z",
7374// "title": "Lisa Says"
7375// }
7376// }],
7377// "total_rows" : 4
7378// }
7379function query(idb, signature, opts) {
7380 // At this stage, in the current implementation, find has already gone through
7381 // and determined if the index already exists from PouchDB's perspective (eg
7382 // there is a design doc for it).
7383 //
7384 // If we find that the index doesn't exist this means we have to close and
7385 // re-open the DB to correct indexes before proceeding, at which point the
7386 // index should exist.
7387
7388 var pdb = this;
7389
7390 // Assumption, there will be only one /, between the design document name
7391 // and the view name.
7392 var parts = signature.split('/');
7393
7394 return new Promise(function (resolve, reject) {
7395 pdb.get('_design/' + parts[0]).then(function (ddoc) {
7396 var fields = rawIndexFields(ddoc, parts[1]);
7397 if (!fields) {
7398 throw new Error('ddoc ' + ddoc._id +' with view ' + parts[1] +
7399 ' does not have map.options.def.fields defined.');
7400 }
7401
7402 var skip = opts.skip;
7403 var limit = Number.isInteger(opts.limit) && opts.limit;
7404
7405 return getIndexHandle(pdb, fields, reject)
7406 .then(function (indexHandle) {
7407 var keyRange = generateKeyRange(opts);
7408 var req = indexHandle.openCursor(keyRange, opts.descending ? 'prev' : 'next');
7409
7410 var rows = [];
7411 req.onerror = idbError(reject);
7412 req.onsuccess = function (e) {
7413 var cursor = e.target.result;
7414
7415 if (!cursor || limit === 0) {
7416 return resolve({
7417 rows: rows
7418 });
7419 }
7420
7421 if (skip) {
7422 cursor.advance(skip);
7423 skip = false;
7424 return;
7425 }
7426
7427 if (limit) {
7428 limit = limit - 1;
7429 }
7430
7431 rows.push({doc: externaliseRecord(cursor.value)});
7432 cursor["continue"]();
7433 };
7434 });
7435 })[
7436 "catch"](reject);
7437 });
7438
7439}
7440
7441function viewCleanup() {
7442 // I'm not sure we have to do anything here.
7443 //
7444 // One option is to just close and re-open the DB, which performs the same
7445 // action. The only reason you'd want to call this is if you deleted a bunch
7446 // of indexes and wanted the space back immediately.
7447 //
7448 // Otherwise index cleanup happens when:
7449 // - A DB is opened
7450 // - A find query is performed against an index that doesn't exist but should
7451
7452 return Promise.resolve();
7453}
7454
7455var ADAPTER_NAME = 'indexeddb';
7456
7457// TODO: Constructor should be capitalised
7458var idbChanges = new Changes();
7459
7460// A shared list of database handles
7461var openDatabases = {};
7462
7463function IdbPouch(dbOpts, callback) {
7464
7465 var api = this;
7466 var metadata = {};
7467
7468 // Wrapper that gives you an active DB handle. You probably want $t.
7469 var $ = function (fun) {
7470 return function () {
7471 var args = Array.prototype.slice.call(arguments);
7472 setup(openDatabases, api, dbOpts).then(function (res) {
7473 metadata = res.metadata;
7474 args.unshift(res.idb);
7475 fun.apply(api, args);
7476 })["catch"](function (err) {
7477 var last = args.unshift();
7478 if (typeof last === 'function') {
7479 last(err);
7480 } else {
7481 throw err;
7482 }
7483 });
7484 };
7485 };
7486 // the promise version of $
7487 var $p = function (fun) {
7488 return function () {
7489 var args = Array.prototype.slice.call(arguments);
7490
7491 return new Promise(function (resolve, reject) {
7492 setup(openDatabases, api, dbOpts).then(function (res) {
7493 metadata = res.metadata;
7494 args.unshift(res.idb);
7495
7496 return fun.apply(api, args);
7497 }).then(resolve)[
7498 "catch"](reject);
7499 });
7500 };
7501 };
7502 // Wrapper that gives you a safe transaction handle. It's important to use
7503 // this instead of opening your own transaction from a db handle got from $,
7504 // because in the time between getting the db handle and opening the
7505 // transaction it may have been invalidated by index changes.
7506 var $t = function (fun, stores, mode) {
7507 stores = stores || [DOC_STORE];
7508 mode = mode || 'readonly';
7509
7510 return function () {
7511 var args = Array.prototype.slice.call(arguments);
7512 var txn = {};
7513 setup(openDatabases, api, dbOpts).then(function (res) {
7514 metadata = res.metadata;
7515 txn.txn = res.idb.transaction(stores, mode);
7516 })["catch"](function (err) {
7517 console.error('Failed to establish transaction safely');
7518 console.error(err);
7519 txn.error = err;
7520 }).then(function () {
7521 args.unshift(txn);
7522 fun.apply(api, args);
7523 });
7524 };
7525 };
7526
7527 api._openTransactionSafely = function (stores, mode, callback) {
7528 $t(function (txn, callback) {
7529 callback(txn.error, txn.txn);
7530 }, stores, mode)(callback);
7531 };
7532
7533 api._remote = false;
7534 api.type = function () { return ADAPTER_NAME; };
7535
7536 api._id = $(function (_, cb) {
7537 cb(null, metadata.db_uuid);
7538 });
7539
7540 api._info = $(function (_, cb) {
7541 return info(metadata, cb);
7542 });
7543
7544 api._get = $t(get);
7545
7546 api._bulkDocs = $(function (_, req, opts, callback) {
7547 bulkDocs(api, req, opts, metadata, dbOpts, idbChanges, callback);
7548 });
7549
7550 api._allDocs = $t(function (txn, opts, cb) {
7551 allDocs(txn, metadata, opts, cb);
7552 });
7553
7554 api._getAttachment = $t(getAttachment);
7555
7556 api._changes = $t(function (txn, opts) {
7557 changes(txn, idbChanges, api, dbOpts, opts);
7558 });
7559
7560 api._getRevisionTree = $t(getRevisionTree);
7561 api._doCompaction = $t(doCompaction, [DOC_STORE], 'readwrite');
7562
7563 api._customFindAbstractMapper = {
7564 query: $p(query),
7565 viewCleanup: $p(viewCleanup)
7566 };
7567
7568 api._destroy = function (opts, callback) {
7569 return destroy(dbOpts, openDatabases, idbChanges, callback);
7570 };
7571
7572 api._close = $(function (db, cb) {
7573 delete openDatabases[dbOpts.name];
7574 db.close();
7575 cb();
7576 });
7577
7578 // Closing and re-opening the DB re-generates native indexes
7579 api._freshen = function () {
7580 return new Promise(function (resolve) {
7581 api._close(function () {
7582 $(resolve)();
7583 });
7584 });
7585 };
7586
7587 // TODO: this setTimeout seems nasty, if its needed lets
7588 // figure out / explain why
7589 setTimeout(function () {
7590 callback(null, api);
7591 });
7592}
7593
7594// TODO: this isnt really valid permanently, just being lazy to start
7595IdbPouch.valid = function () {
7596 return true;
7597};
7598
7599function next (PouchDB) {
7600 PouchDB.adapter(ADAPTER_NAME, IdbPouch, true);
7601}
7602
7603// dead simple promise pool, inspired by https://github.com/timdp/es6-promise-pool
7604// but much smaller in code size. limits the number of concurrent promises that are executed
7605
7606
7607function pool(promiseFactories, limit) {
7608 return new Promise(function (resolve, reject) {
7609 var running = 0;
7610 var current = 0;
7611 var done = 0;
7612 var len = promiseFactories.length;
7613 var err;
7614
7615 function runNext() {
7616 running++;
7617 promiseFactories[current++]().then(onSuccess, onError);
7618 }
7619
7620 function doNext() {
7621 if (++done === len) {
7622 /* istanbul ignore if */
7623 if (err) {
7624 reject(err);
7625 } else {
7626 resolve();
7627 }
7628 } else {
7629 runNextBatch();
7630 }
7631 }
7632
7633 function onSuccess() {
7634 running--;
7635 doNext();
7636 }
7637
7638 /* istanbul ignore next */
7639 function onError(thisErr) {
7640 running--;
7641 err = err || thisErr;
7642 doNext();
7643 }
7644
7645 function runNextBatch() {
7646 while (running < limit && current < len) {
7647 runNext();
7648 }
7649 }
7650
7651 runNextBatch();
7652 });
7653}
7654
7655var CHANGES_BATCH_SIZE = 25;
7656var MAX_SIMULTANEOUS_REVS = 50;
7657var CHANGES_TIMEOUT_BUFFER = 5000;
7658var DEFAULT_HEARTBEAT = 10000;
7659
7660var supportsBulkGetMap = {};
7661
7662function readAttachmentsAsBlobOrBuffer(row) {
7663 var doc = row.doc || row.ok;
7664 var atts = doc && doc._attachments;
7665 if (!atts) {
7666 return;
7667 }
7668 Object.keys(atts).forEach(function (filename) {
7669 var att = atts[filename];
7670 att.data = b64ToBluffer(att.data, att.content_type);
7671 });
7672}
7673
7674function encodeDocId(id) {
7675 if (/^_design/.test(id)) {
7676 return '_design/' + encodeURIComponent(id.slice(8));
7677 }
7678 if (/^_local/.test(id)) {
7679 return '_local/' + encodeURIComponent(id.slice(7));
7680 }
7681 return encodeURIComponent(id);
7682}
7683
7684function preprocessAttachments$1(doc) {
7685 if (!doc._attachments || !Object.keys(doc._attachments)) {
7686 return Promise.resolve();
7687 }
7688
7689 return Promise.all(Object.keys(doc._attachments).map(function (key) {
7690 var attachment = doc._attachments[key];
7691 if (attachment.data && typeof attachment.data !== 'string') {
7692 return new Promise(function (resolve) {
7693 blobToBase64(attachment.data, resolve);
7694 }).then(function (b64) {
7695 attachment.data = b64;
7696 });
7697 }
7698 }));
7699}
7700
7701function hasUrlPrefix(opts) {
7702 if (!opts.prefix) {
7703 return false;
7704 }
7705 var protocol = parseUri(opts.prefix).protocol;
7706 return protocol === 'http' || protocol === 'https';
7707}
7708
7709// Get all the information you possibly can about the URI given by name and
7710// return it as a suitable object.
7711function getHost(name, opts) {
7712 // encode db name if opts.prefix is a url (#5574)
7713 if (hasUrlPrefix(opts)) {
7714 var dbName = opts.name.substr(opts.prefix.length);
7715 // Ensure prefix has a trailing slash
7716 var prefix = opts.prefix.replace(/\/?$/, '/');
7717 name = prefix + encodeURIComponent(dbName);
7718 }
7719
7720 var uri = parseUri(name);
7721 if (uri.user || uri.password) {
7722 uri.auth = {username: uri.user, password: uri.password};
7723 }
7724
7725 // Split the path part of the URI into parts using '/' as the delimiter
7726 // after removing any leading '/' and any trailing '/'
7727 var parts = uri.path.replace(/(^\/|\/$)/g, '').split('/');
7728
7729 uri.db = parts.pop();
7730 // Prevent double encoding of URI component
7731 if (uri.db.indexOf('%') === -1) {
7732 uri.db = encodeURIComponent(uri.db);
7733 }
7734
7735 uri.path = parts.join('/');
7736
7737 return uri;
7738}
7739
7740// Generate a URL with the host data given by opts and the given path
7741function genDBUrl(opts, path) {
7742 return genUrl(opts, opts.db + '/' + path);
7743}
7744
7745// Generate a URL with the host data given by opts and the given path
7746function genUrl(opts, path) {
7747 // If the host already has a path, then we need to have a path delimiter
7748 // Otherwise, the path delimiter is the empty string
7749 var pathDel = !opts.path ? '' : '/';
7750
7751 // If the host already has a path, then we need to have a path delimiter
7752 // Otherwise, the path delimiter is the empty string
7753 return opts.protocol + '://' + opts.host +
7754 (opts.port ? (':' + opts.port) : '') +
7755 '/' + opts.path + pathDel + path;
7756}
7757
7758function paramsToStr(params) {
7759 return '?' + Object.keys(params).map(function (k) {
7760 return k + '=' + encodeURIComponent(params[k]);
7761 }).join('&');
7762}
7763
7764function shouldCacheBust(opts) {
7765 var ua = (typeof navigator !== 'undefined' && navigator.userAgent) ?
7766 navigator.userAgent.toLowerCase() : '';
7767 var isIE = ua.indexOf('msie') !== -1;
7768 var isTrident = ua.indexOf('trident') !== -1;
7769 var isEdge = ua.indexOf('edge') !== -1;
7770 var isGET = !('method' in opts) || opts.method === 'GET';
7771 return (isIE || isTrident || isEdge) && isGET;
7772}
7773
7774// Implements the PouchDB API for dealing with CouchDB instances over HTTP
7775function HttpPouch(opts, callback) {
7776
7777 // The functions that will be publicly available for HttpPouch
7778 var api = this;
7779
7780 var host = getHost(opts.name, opts);
7781 var dbUrl = genDBUrl(host, '');
7782
7783 opts = clone(opts);
7784
7785 var ourFetch = function (url, options) {
7786
7787 options = options || {};
7788 options.headers = options.headers || new h();
7789
7790 options.credentials = 'include';
7791
7792 if (opts.auth || host.auth) {
7793 var nAuth = opts.auth || host.auth;
7794 var str = nAuth.username + ':' + nAuth.password;
7795 var token = thisBtoa(unescape(encodeURIComponent(str)));
7796 options.headers.set('Authorization', 'Basic ' + token);
7797 }
7798
7799 var headers = opts.headers || {};
7800 Object.keys(headers).forEach(function (key) {
7801 options.headers.append(key, headers[key]);
7802 });
7803
7804 /* istanbul ignore if */
7805 if (shouldCacheBust(options)) {
7806 url += (url.indexOf('?') === -1 ? '?' : '&') + '_nonce=' + Date.now();
7807 }
7808
7809 var fetchFun = opts.fetch || f$1;
7810 return fetchFun(url, options);
7811 };
7812
7813 function adapterFun$$1(name, fun) {
7814 return adapterFun(name, getArguments(function (args) {
7815 setup().then(function () {
7816 return fun.apply(this, args);
7817 })["catch"](function (e) {
7818 var callback = args.pop();
7819 callback(e);
7820 });
7821 })).bind(api);
7822 }
7823
7824 function fetchJSON(url, options, callback) {
7825
7826 var result = {};
7827
7828 options = options || {};
7829 options.headers = options.headers || new h();
7830
7831 if (!options.headers.get('Content-Type')) {
7832 options.headers.set('Content-Type', 'application/json');
7833 }
7834 if (!options.headers.get('Accept')) {
7835 options.headers.set('Accept', 'application/json');
7836 }
7837
7838 return ourFetch(url, options).then(function (response) {
7839 result.ok = response.ok;
7840 result.status = response.status;
7841 return response.json();
7842 }).then(function (json) {
7843 result.data = json;
7844 if (!result.ok) {
7845 result.data.status = result.status;
7846 var err = generateErrorFromResponse(result.data);
7847 if (callback) {
7848 return callback(err);
7849 } else {
7850 throw err;
7851 }
7852 }
7853
7854 if (Array.isArray(result.data)) {
7855 result.data = result.data.map(function (v) {
7856 if (v.error || v.missing) {
7857 return generateErrorFromResponse(v);
7858 } else {
7859 return v;
7860 }
7861 });
7862 }
7863
7864 if (callback) {
7865 callback(null, result.data);
7866 } else {
7867 return result;
7868 }
7869 });
7870 }
7871
7872 var setupPromise;
7873
7874 function setup() {
7875 if (opts.skip_setup) {
7876 return Promise.resolve();
7877 }
7878
7879 // If there is a setup in process or previous successful setup
7880 // done then we will use that
7881 // If previous setups have been rejected we will try again
7882 if (setupPromise) {
7883 return setupPromise;
7884 }
7885
7886 setupPromise = fetchJSON(dbUrl)["catch"](function (err) {
7887 if (err && err.status && err.status === 404) {
7888 // Doesnt exist, create it
7889 explainError(404, 'PouchDB is just detecting if the remote exists.');
7890 return fetchJSON(dbUrl, {method: 'PUT'});
7891 } else {
7892 return Promise.reject(err);
7893 }
7894 })["catch"](function (err) {
7895 // If we try to create a database that already exists, skipped in
7896 // istanbul since its catching a race condition.
7897 /* istanbul ignore if */
7898 if (err && err.status && err.status === 412) {
7899 return true;
7900 }
7901 return Promise.reject(err);
7902 });
7903
7904 setupPromise["catch"](function () {
7905 setupPromise = null;
7906 });
7907
7908 return setupPromise;
7909 }
7910
7911 immediate(function () {
7912 callback(null, api);
7913 });
7914
7915 api._remote = true;
7916
7917 /* istanbul ignore next */
7918 api.type = function () {
7919 return 'http';
7920 };
7921
7922 api.id = adapterFun$$1('id', function (callback) {
7923 ourFetch(genUrl(host, '')).then(function (response) {
7924 return response.json();
7925 })["catch"](function () {
7926 return {};
7927 }).then(function (result) {
7928 // Bad response or missing `uuid` should not prevent ID generation.
7929 var uuid$$1 = (result && result.uuid) ?
7930 (result.uuid + host.db) : genDBUrl(host, '');
7931 callback(null, uuid$$1);
7932 });
7933 });
7934
7935 // Sends a POST request to the host calling the couchdb _compact function
7936 // version: The version of CouchDB it is running
7937 api.compact = adapterFun$$1('compact', function (opts, callback) {
7938 if (typeof opts === 'function') {
7939 callback = opts;
7940 opts = {};
7941 }
7942 opts = clone(opts);
7943
7944 fetchJSON(genDBUrl(host, '_compact'), {method: 'POST'}).then(function () {
7945 function ping() {
7946 api.info(function (err, res) {
7947 // CouchDB may send a "compact_running:true" if it's
7948 // already compacting. PouchDB Server doesn't.
7949 /* istanbul ignore else */
7950 if (res && !res.compact_running) {
7951 callback(null, {ok: true});
7952 } else {
7953 setTimeout(ping, opts.interval || 200);
7954 }
7955 });
7956 }
7957 // Ping the http if it's finished compaction
7958 ping();
7959 });
7960 });
7961
7962 api.bulkGet = adapterFun('bulkGet', function (opts, callback) {
7963 var self = this;
7964
7965 function doBulkGet(cb) {
7966 var params = {};
7967 if (opts.revs) {
7968 params.revs = true;
7969 }
7970 if (opts.attachments) {
7971 /* istanbul ignore next */
7972 params.attachments = true;
7973 }
7974 if (opts.latest) {
7975 params.latest = true;
7976 }
7977 fetchJSON(genDBUrl(host, '_bulk_get' + paramsToStr(params)), {
7978 method: 'POST',
7979 body: JSON.stringify({ docs: opts.docs})
7980 }).then(function (result) {
7981 if (opts.attachments && opts.binary) {
7982 result.data.results.forEach(function (res) {
7983 res.docs.forEach(readAttachmentsAsBlobOrBuffer);
7984 });
7985 }
7986 cb(null, result.data);
7987 })["catch"](cb);
7988 }
7989
7990 /* istanbul ignore next */
7991 function doBulkGetShim() {
7992 // avoid "url too long error" by splitting up into multiple requests
7993 var batchSize = MAX_SIMULTANEOUS_REVS;
7994 var numBatches = Math.ceil(opts.docs.length / batchSize);
7995 var numDone = 0;
7996 var results = new Array(numBatches);
7997
7998 function onResult(batchNum) {
7999 return function (err, res) {
8000 // err is impossible because shim returns a list of errs in that case
8001 results[batchNum] = res.results;
8002 if (++numDone === numBatches) {
8003 callback(null, {results: flatten(results)});
8004 }
8005 };
8006 }
8007
8008 for (var i = 0; i < numBatches; i++) {
8009 var subOpts = pick(opts, ['revs', 'attachments', 'binary', 'latest']);
8010 subOpts.docs = opts.docs.slice(i * batchSize,
8011 Math.min(opts.docs.length, (i + 1) * batchSize));
8012 bulkGet(self, subOpts, onResult(i));
8013 }
8014 }
8015
8016 // mark the whole database as either supporting or not supporting _bulk_get
8017 var dbUrl = genUrl(host, '');
8018 var supportsBulkGet = supportsBulkGetMap[dbUrl];
8019
8020 /* istanbul ignore next */
8021 if (typeof supportsBulkGet !== 'boolean') {
8022 // check if this database supports _bulk_get
8023 doBulkGet(function (err, res) {
8024 if (err) {
8025 supportsBulkGetMap[dbUrl] = false;
8026 explainError(
8027 err.status,
8028 'PouchDB is just detecting if the remote ' +
8029 'supports the _bulk_get API.'
8030 );
8031 doBulkGetShim();
8032 } else {
8033 supportsBulkGetMap[dbUrl] = true;
8034 callback(null, res);
8035 }
8036 });
8037 } else if (supportsBulkGet) {
8038 doBulkGet(callback);
8039 } else {
8040 doBulkGetShim();
8041 }
8042 });
8043
8044 // Calls GET on the host, which gets back a JSON string containing
8045 // couchdb: A welcome string
8046 // version: The version of CouchDB it is running
8047 api._info = function (callback) {
8048 setup().then(function () {
8049 return ourFetch(genDBUrl(host, ''));
8050 }).then(function (response) {
8051 return response.json();
8052 }).then(function (info) {
8053 info.host = genDBUrl(host, '');
8054 callback(null, info);
8055 })["catch"](callback);
8056 };
8057
8058 api.fetch = function (path, options) {
8059 return setup().then(function () {
8060 var url = path.substring(0, 1) === '/' ?
8061 genUrl(host, path.substring(1)) :
8062 genDBUrl(host, path);
8063 return ourFetch(url, options);
8064 });
8065 };
8066
8067 // Get the document with the given id from the database given by host.
8068 // The id could be solely the _id in the database, or it may be a
8069 // _design/ID or _local/ID path
8070 api.get = adapterFun$$1('get', function (id, opts, callback) {
8071 // If no options were given, set the callback to the second parameter
8072 if (typeof opts === 'function') {
8073 callback = opts;
8074 opts = {};
8075 }
8076 opts = clone(opts);
8077
8078 // List of parameters to add to the GET request
8079 var params = {};
8080
8081 if (opts.revs) {
8082 params.revs = true;
8083 }
8084
8085 if (opts.revs_info) {
8086 params.revs_info = true;
8087 }
8088
8089 if (opts.latest) {
8090 params.latest = true;
8091 }
8092
8093 if (opts.open_revs) {
8094 if (opts.open_revs !== "all") {
8095 opts.open_revs = JSON.stringify(opts.open_revs);
8096 }
8097 params.open_revs = opts.open_revs;
8098 }
8099
8100 if (opts.rev) {
8101 params.rev = opts.rev;
8102 }
8103
8104 if (opts.conflicts) {
8105 params.conflicts = opts.conflicts;
8106 }
8107
8108 /* istanbul ignore if */
8109 if (opts.update_seq) {
8110 params.update_seq = opts.update_seq;
8111 }
8112
8113 id = encodeDocId(id);
8114
8115 function fetchAttachments(doc) {
8116 var atts = doc._attachments;
8117 var filenames = atts && Object.keys(atts);
8118 if (!atts || !filenames.length) {
8119 return;
8120 }
8121 // we fetch these manually in separate XHRs, because
8122 // Sync Gateway would normally send it back as multipart/mixed,
8123 // which we cannot parse. Also, this is more efficient than
8124 // receiving attachments as base64-encoded strings.
8125 function fetchData(filename) {
8126 var att = atts[filename];
8127 var path = encodeDocId(doc._id) + '/' + encodeAttachmentId(filename) +
8128 '?rev=' + doc._rev;
8129 return ourFetch(genDBUrl(host, path)).then(function (response) {
8130 if (typeof process !== 'undefined' && !process.browser) {
8131 return response.buffer();
8132 } else {
8133 /* istanbul ignore next */
8134 return response.blob();
8135 }
8136 }).then(function (blob) {
8137 if (opts.binary) {
8138 // TODO: Can we remove this?
8139 if (typeof process !== 'undefined' && !process.browser) {
8140 blob.type = att.content_type;
8141 }
8142 return blob;
8143 }
8144 return new Promise(function (resolve) {
8145 blobToBase64(blob, resolve);
8146 });
8147 }).then(function (data) {
8148 delete att.stub;
8149 delete att.length;
8150 att.data = data;
8151 });
8152 }
8153
8154 var promiseFactories = filenames.map(function (filename) {
8155 return function () {
8156 return fetchData(filename);
8157 };
8158 });
8159
8160 // This limits the number of parallel xhr requests to 5 any time
8161 // to avoid issues with maximum browser request limits
8162 return pool(promiseFactories, 5);
8163 }
8164
8165 function fetchAllAttachments(docOrDocs) {
8166 if (Array.isArray(docOrDocs)) {
8167 return Promise.all(docOrDocs.map(function (doc) {
8168 if (doc.ok) {
8169 return fetchAttachments(doc.ok);
8170 }
8171 }));
8172 }
8173 return fetchAttachments(docOrDocs);
8174 }
8175
8176 var url = genDBUrl(host, id + paramsToStr(params));
8177 fetchJSON(url).then(function (res) {
8178 return Promise.resolve().then(function () {
8179 if (opts.attachments) {
8180 return fetchAllAttachments(res.data);
8181 }
8182 }).then(function () {
8183 callback(null, res.data);
8184 });
8185 })["catch"](function (e) {
8186 e.docId = id;
8187 callback(e);
8188 });
8189 });
8190
8191
8192 // Delete the document given by doc from the database given by host.
8193 api.remove = adapterFun$$1('remove', function (docOrId, optsOrRev, opts, cb) {
8194 var doc;
8195 if (typeof optsOrRev === 'string') {
8196 // id, rev, opts, callback style
8197 doc = {
8198 _id: docOrId,
8199 _rev: optsOrRev
8200 };
8201 if (typeof opts === 'function') {
8202 cb = opts;
8203 opts = {};
8204 }
8205 } else {
8206 // doc, opts, callback style
8207 doc = docOrId;
8208 if (typeof optsOrRev === 'function') {
8209 cb = optsOrRev;
8210 opts = {};
8211 } else {
8212 cb = opts;
8213 opts = optsOrRev;
8214 }
8215 }
8216
8217 var rev$$1 = (doc._rev || opts.rev);
8218 var url = genDBUrl(host, encodeDocId(doc._id)) + '?rev=' + rev$$1;
8219
8220 fetchJSON(url, {method: 'DELETE'}, cb)["catch"](cb);
8221 });
8222
8223 function encodeAttachmentId(attachmentId) {
8224 return attachmentId.split("/").map(encodeURIComponent).join("/");
8225 }
8226
8227 // Get the attachment
8228 api.getAttachment = adapterFun$$1('getAttachment', function (docId, attachmentId,
8229 opts, callback) {
8230 if (typeof opts === 'function') {
8231 callback = opts;
8232 opts = {};
8233 }
8234 var params = opts.rev ? ('?rev=' + opts.rev) : '';
8235 var url = genDBUrl(host, encodeDocId(docId)) + '/' +
8236 encodeAttachmentId(attachmentId) + params;
8237 var contentType;
8238 ourFetch(url, {method: 'GET'}).then(function (response) {
8239 contentType = response.headers.get('content-type');
8240 if (!response.ok) {
8241 throw response;
8242 } else {
8243 if (typeof process !== 'undefined' && !process.browser) {
8244 return response.buffer();
8245 } else {
8246 /* istanbul ignore next */
8247 return response.blob();
8248 }
8249 }
8250 }).then(function (blob) {
8251 // TODO: also remove
8252 if (typeof process !== 'undefined' && !process.browser) {
8253 blob.type = contentType;
8254 }
8255 callback(null, blob);
8256 })["catch"](function (err) {
8257 callback(err);
8258 });
8259 });
8260
8261 // Remove the attachment given by the id and rev
8262 api.removeAttachment = adapterFun$$1('removeAttachment', function (docId,
8263 attachmentId,
8264 rev$$1,
8265 callback) {
8266 var url = genDBUrl(host, encodeDocId(docId) + '/' +
8267 encodeAttachmentId(attachmentId)) + '?rev=' + rev$$1;
8268 fetchJSON(url, {method: 'DELETE'}, callback)["catch"](callback);
8269 });
8270
8271 // Add the attachment given by blob and its contentType property
8272 // to the document with the given id, the revision given by rev, and
8273 // add it to the database given by host.
8274 api.putAttachment = adapterFun$$1('putAttachment', function (docId, attachmentId,
8275 rev$$1, blob,
8276 type, callback) {
8277 if (typeof type === 'function') {
8278 callback = type;
8279 type = blob;
8280 blob = rev$$1;
8281 rev$$1 = null;
8282 }
8283 var id = encodeDocId(docId) + '/' + encodeAttachmentId(attachmentId);
8284 var url = genDBUrl(host, id);
8285 if (rev$$1) {
8286 url += '?rev=' + rev$$1;
8287 }
8288
8289 if (typeof blob === 'string') {
8290 // input is assumed to be a base64 string
8291 var binary;
8292 try {
8293 binary = thisAtob(blob);
8294 } catch (err) {
8295 return callback(createError(BAD_ARG,
8296 'Attachment is not a valid base64 string'));
8297 }
8298 blob = binary ? binStringToBluffer(binary, type) : '';
8299 }
8300
8301 // Add the attachment
8302 fetchJSON(url, {
8303 headers: new h({'Content-Type': type}),
8304 method: 'PUT',
8305 body: blob
8306 }, callback)["catch"](callback);
8307 });
8308
8309 // Update/create multiple documents given by req in the database
8310 // given by host.
8311 api._bulkDocs = function (req, opts, callback) {
8312 // If new_edits=false then it prevents the database from creating
8313 // new revision numbers for the documents. Instead it just uses
8314 // the old ones. This is used in database replication.
8315 req.new_edits = opts.new_edits;
8316
8317 setup().then(function () {
8318 return Promise.all(req.docs.map(preprocessAttachments$1));
8319 }).then(function () {
8320 // Update/create the documents
8321 return fetchJSON(genDBUrl(host, '_bulk_docs'), {
8322 method: 'POST',
8323 body: JSON.stringify(req)
8324 }, callback);
8325 })["catch"](callback);
8326 };
8327
8328
8329 // Update/create document
8330 api._put = function (doc, opts, callback) {
8331 setup().then(function () {
8332 return preprocessAttachments$1(doc);
8333 }).then(function () {
8334 return fetchJSON(genDBUrl(host, encodeDocId(doc._id)), {
8335 method: 'PUT',
8336 body: JSON.stringify(doc)
8337 });
8338 }).then(function (result) {
8339 callback(null, result.data);
8340 })["catch"](function (err) {
8341 err.docId = doc && doc._id;
8342 callback(err);
8343 });
8344 };
8345
8346
8347 // Get a listing of the documents in the database given
8348 // by host and ordered by increasing id.
8349 api.allDocs = adapterFun$$1('allDocs', function (opts, callback) {
8350 if (typeof opts === 'function') {
8351 callback = opts;
8352 opts = {};
8353 }
8354 opts = clone(opts);
8355
8356 // List of parameters to add to the GET request
8357 var params = {};
8358 var body;
8359 var method = 'GET';
8360
8361 if (opts.conflicts) {
8362 params.conflicts = true;
8363 }
8364
8365 /* istanbul ignore if */
8366 if (opts.update_seq) {
8367 params.update_seq = true;
8368 }
8369
8370 if (opts.descending) {
8371 params.descending = true;
8372 }
8373
8374 if (opts.include_docs) {
8375 params.include_docs = true;
8376 }
8377
8378 // added in CouchDB 1.6.0
8379 if (opts.attachments) {
8380 params.attachments = true;
8381 }
8382
8383 if (opts.key) {
8384 params.key = JSON.stringify(opts.key);
8385 }
8386
8387 if (opts.start_key) {
8388 opts.startkey = opts.start_key;
8389 }
8390
8391 if (opts.startkey) {
8392 params.startkey = JSON.stringify(opts.startkey);
8393 }
8394
8395 if (opts.end_key) {
8396 opts.endkey = opts.end_key;
8397 }
8398
8399 if (opts.endkey) {
8400 params.endkey = JSON.stringify(opts.endkey);
8401 }
8402
8403 if (typeof opts.inclusive_end !== 'undefined') {
8404 params.inclusive_end = !!opts.inclusive_end;
8405 }
8406
8407 if (typeof opts.limit !== 'undefined') {
8408 params.limit = opts.limit;
8409 }
8410
8411 if (typeof opts.skip !== 'undefined') {
8412 params.skip = opts.skip;
8413 }
8414
8415 var paramStr = paramsToStr(params);
8416
8417 if (typeof opts.keys !== 'undefined') {
8418 method = 'POST';
8419 body = {keys: opts.keys};
8420 }
8421
8422 fetchJSON(genDBUrl(host, '_all_docs' + paramStr), {
8423 method: method,
8424 body: JSON.stringify(body)
8425 }).then(function (result) {
8426 if (opts.include_docs && opts.attachments && opts.binary) {
8427 result.data.rows.forEach(readAttachmentsAsBlobOrBuffer);
8428 }
8429 callback(null, result.data);
8430 })["catch"](callback);
8431 });
8432
8433 // Get a list of changes made to documents in the database given by host.
8434 // TODO According to the README, there should be two other methods here,
8435 // api.changes.addListener and api.changes.removeListener.
8436 api._changes = function (opts) {
8437
8438 // We internally page the results of a changes request, this means
8439 // if there is a large set of changes to be returned we can start
8440 // processing them quicker instead of waiting on the entire
8441 // set of changes to return and attempting to process them at once
8442 var batchSize = 'batch_size' in opts ? opts.batch_size : CHANGES_BATCH_SIZE;
8443
8444 opts = clone(opts);
8445
8446 if (opts.continuous && !('heartbeat' in opts)) {
8447 opts.heartbeat = DEFAULT_HEARTBEAT;
8448 }
8449
8450 var requestTimeout = ('timeout' in opts) ? opts.timeout : 30 * 1000;
8451
8452 // ensure CHANGES_TIMEOUT_BUFFER applies
8453 if ('timeout' in opts && opts.timeout &&
8454 (requestTimeout - opts.timeout) < CHANGES_TIMEOUT_BUFFER) {
8455 requestTimeout = opts.timeout + CHANGES_TIMEOUT_BUFFER;
8456 }
8457
8458 /* istanbul ignore if */
8459 if ('heartbeat' in opts && opts.heartbeat &&
8460 (requestTimeout - opts.heartbeat) < CHANGES_TIMEOUT_BUFFER) {
8461 requestTimeout = opts.heartbeat + CHANGES_TIMEOUT_BUFFER;
8462 }
8463
8464 var params = {};
8465 if ('timeout' in opts && opts.timeout) {
8466 params.timeout = opts.timeout;
8467 }
8468
8469 var limit = (typeof opts.limit !== 'undefined') ? opts.limit : false;
8470 var leftToFetch = limit;
8471
8472 if (opts.style) {
8473 params.style = opts.style;
8474 }
8475
8476 if (opts.include_docs || opts.filter && typeof opts.filter === 'function') {
8477 params.include_docs = true;
8478 }
8479
8480 if (opts.attachments) {
8481 params.attachments = true;
8482 }
8483
8484 if (opts.continuous) {
8485 params.feed = 'longpoll';
8486 }
8487
8488 if (opts.seq_interval) {
8489 params.seq_interval = opts.seq_interval;
8490 }
8491
8492 if (opts.conflicts) {
8493 params.conflicts = true;
8494 }
8495
8496 if (opts.descending) {
8497 params.descending = true;
8498 }
8499
8500 /* istanbul ignore if */
8501 if (opts.update_seq) {
8502 params.update_seq = true;
8503 }
8504
8505 if ('heartbeat' in opts) {
8506 // If the heartbeat value is false, it disables the default heartbeat
8507 if (opts.heartbeat) {
8508 params.heartbeat = opts.heartbeat;
8509 }
8510 }
8511
8512 if (opts.filter && typeof opts.filter === 'string') {
8513 params.filter = opts.filter;
8514 }
8515
8516 if (opts.view && typeof opts.view === 'string') {
8517 params.filter = '_view';
8518 params.view = opts.view;
8519 }
8520
8521 // If opts.query_params exists, pass it through to the changes request.
8522 // These parameters may be used by the filter on the source database.
8523 if (opts.query_params && typeof opts.query_params === 'object') {
8524 for (var param_name in opts.query_params) {
8525 /* istanbul ignore else */
8526 if (opts.query_params.hasOwnProperty(param_name)) {
8527 params[param_name] = opts.query_params[param_name];
8528 }
8529 }
8530 }
8531
8532 var method = 'GET';
8533 var body;
8534
8535 if (opts.doc_ids) {
8536 // set this automagically for the user; it's annoying that couchdb
8537 // requires both a "filter" and a "doc_ids" param.
8538 params.filter = '_doc_ids';
8539 method = 'POST';
8540 body = {doc_ids: opts.doc_ids };
8541 }
8542 /* istanbul ignore next */
8543 else if (opts.selector) {
8544 // set this automagically for the user, similar to above
8545 params.filter = '_selector';
8546 method = 'POST';
8547 body = {selector: opts.selector };
8548 }
8549
8550 var controller = new a();
8551 var lastFetchedSeq;
8552
8553 // Get all the changes starting wtih the one immediately after the
8554 // sequence number given by since.
8555 var fetchData = function (since, callback) {
8556 if (opts.aborted) {
8557 return;
8558 }
8559 params.since = since;
8560 // "since" can be any kind of json object in Cloudant/CouchDB 2.x
8561 /* istanbul ignore next */
8562 if (typeof params.since === "object") {
8563 params.since = JSON.stringify(params.since);
8564 }
8565
8566 if (opts.descending) {
8567 if (limit) {
8568 params.limit = leftToFetch;
8569 }
8570 } else {
8571 params.limit = (!limit || leftToFetch > batchSize) ?
8572 batchSize : leftToFetch;
8573 }
8574
8575 // Set the options for the ajax call
8576 var url = genDBUrl(host, '_changes' + paramsToStr(params));
8577 var fetchOpts = {
8578 signal: controller.signal,
8579 method: method,
8580 body: JSON.stringify(body)
8581 };
8582 lastFetchedSeq = since;
8583
8584 /* istanbul ignore if */
8585 if (opts.aborted) {
8586 return;
8587 }
8588
8589 // Get the changes
8590 setup().then(function () {
8591 return fetchJSON(url, fetchOpts, callback);
8592 })["catch"](callback);
8593 };
8594
8595 // If opts.since exists, get all the changes from the sequence
8596 // number given by opts.since. Otherwise, get all the changes
8597 // from the sequence number 0.
8598 var results = {results: []};
8599
8600 var fetched = function (err, res) {
8601 if (opts.aborted) {
8602 return;
8603 }
8604 var raw_results_length = 0;
8605 // If the result of the ajax call (res) contains changes (res.results)
8606 if (res && res.results) {
8607 raw_results_length = res.results.length;
8608 results.last_seq = res.last_seq;
8609 var pending = null;
8610 var lastSeq = null;
8611 // Attach 'pending' property if server supports it (CouchDB 2.0+)
8612 /* istanbul ignore if */
8613 if (typeof res.pending === 'number') {
8614 pending = res.pending;
8615 }
8616 if (typeof results.last_seq === 'string' || typeof results.last_seq === 'number') {
8617 lastSeq = results.last_seq;
8618 }
8619 // For each change
8620 var req = {};
8621 req.query = opts.query_params;
8622 res.results = res.results.filter(function (c) {
8623 leftToFetch--;
8624 var ret = filterChange(opts)(c);
8625 if (ret) {
8626 if (opts.include_docs && opts.attachments && opts.binary) {
8627 readAttachmentsAsBlobOrBuffer(c);
8628 }
8629 if (opts.return_docs) {
8630 results.results.push(c);
8631 }
8632 opts.onChange(c, pending, lastSeq);
8633 }
8634 return ret;
8635 });
8636 } else if (err) {
8637 // In case of an error, stop listening for changes and call
8638 // opts.complete
8639 opts.aborted = true;
8640 opts.complete(err);
8641 return;
8642 }
8643
8644 // The changes feed may have timed out with no results
8645 // if so reuse last update sequence
8646 if (res && res.last_seq) {
8647 lastFetchedSeq = res.last_seq;
8648 }
8649
8650 var finished = (limit && leftToFetch <= 0) ||
8651 (res && raw_results_length < batchSize) ||
8652 (opts.descending);
8653
8654 if ((opts.continuous && !(limit && leftToFetch <= 0)) || !finished) {
8655 // Queue a call to fetch again with the newest sequence number
8656 immediate(function () { fetchData(lastFetchedSeq, fetched); });
8657 } else {
8658 // We're done, call the callback
8659 opts.complete(null, results);
8660 }
8661 };
8662
8663 fetchData(opts.since || 0, fetched);
8664
8665 // Return a method to cancel this method from processing any more
8666 return {
8667 cancel: function () {
8668 opts.aborted = true;
8669 controller.abort();
8670 }
8671 };
8672 };
8673
8674 // Given a set of document/revision IDs (given by req), tets the subset of
8675 // those that do NOT correspond to revisions stored in the database.
8676 // See http://wiki.apache.org/couchdb/HttpPostRevsDiff
8677 api.revsDiff = adapterFun$$1('revsDiff', function (req, opts, callback) {
8678 // If no options were given, set the callback to be the second parameter
8679 if (typeof opts === 'function') {
8680 callback = opts;
8681 opts = {};
8682 }
8683
8684 // Get the missing document/revision IDs
8685 fetchJSON(genDBUrl(host, '_revs_diff'), {
8686 method: 'POST',
8687 body: JSON.stringify(req)
8688 }, callback)["catch"](callback);
8689 });
8690
8691 api._close = function (callback) {
8692 callback();
8693 };
8694
8695 api._destroy = function (options, callback) {
8696 fetchJSON(genDBUrl(host, ''), {method: 'DELETE'}).then(function (json) {
8697 callback(null, json);
8698 })["catch"](function (err) {
8699 /* istanbul ignore if */
8700 if (err.status === 404) {
8701 callback(null, {ok: true});
8702 } else {
8703 callback(err);
8704 }
8705 });
8706 };
8707}
8708
8709// HttpPouch is a valid adapter.
8710HttpPouch.valid = function () {
8711 return true;
8712};
8713
8714function httpPouch (PouchDB) {
8715 PouchDB.adapter('http', HttpPouch, false);
8716 PouchDB.adapter('https', HttpPouch, false);
8717}
8718
8719function QueryParseError(message) {
8720 this.status = 400;
8721 this.name = 'query_parse_error';
8722 this.message = message;
8723 this.error = true;
8724 try {
8725 Error.captureStackTrace(this, QueryParseError);
8726 } catch (e) {}
8727}
8728
8729inherits(QueryParseError, Error);
8730
8731function NotFoundError(message) {
8732 this.status = 404;
8733 this.name = 'not_found';
8734 this.message = message;
8735 this.error = true;
8736 try {
8737 Error.captureStackTrace(this, NotFoundError);
8738 } catch (e) {}
8739}
8740
8741inherits(NotFoundError, Error);
8742
8743function BuiltInError(message) {
8744 this.status = 500;
8745 this.name = 'invalid_value';
8746 this.message = message;
8747 this.error = true;
8748 try {
8749 Error.captureStackTrace(this, BuiltInError);
8750 } catch (e) {}
8751}
8752
8753inherits(BuiltInError, Error);
8754
8755function promisedCallback(promise, callback) {
8756 if (callback) {
8757 promise.then(function (res) {
8758 immediate(function () {
8759 callback(null, res);
8760 });
8761 }, function (reason) {
8762 immediate(function () {
8763 callback(reason);
8764 });
8765 });
8766 }
8767 return promise;
8768}
8769
8770function callbackify(fun) {
8771 return getArguments(function (args) {
8772 var cb = args.pop();
8773 var promise = fun.apply(this, args);
8774 if (typeof cb === 'function') {
8775 promisedCallback(promise, cb);
8776 }
8777 return promise;
8778 });
8779}
8780
8781// Promise finally util similar to Q.finally
8782function fin(promise, finalPromiseFactory) {
8783 return promise.then(function (res) {
8784 return finalPromiseFactory().then(function () {
8785 return res;
8786 });
8787 }, function (reason) {
8788 return finalPromiseFactory().then(function () {
8789 throw reason;
8790 });
8791 });
8792}
8793
8794function sequentialize(queue, promiseFactory) {
8795 return function () {
8796 var args = arguments;
8797 var that = this;
8798 return queue.add(function () {
8799 return promiseFactory.apply(that, args);
8800 });
8801 };
8802}
8803
8804// uniq an array of strings, order not guaranteed
8805// similar to underscore/lodash _.uniq
8806function uniq(arr) {
8807 var theSet = new ExportedSet(arr);
8808 var result = new Array(theSet.size);
8809 var index = -1;
8810 theSet.forEach(function (value) {
8811 result[++index] = value;
8812 });
8813 return result;
8814}
8815
8816function mapToKeysArray(map) {
8817 var result = new Array(map.size);
8818 var index = -1;
8819 map.forEach(function (value, key) {
8820 result[++index] = key;
8821 });
8822 return result;
8823}
8824
8825function createBuiltInError(name) {
8826 var message = 'builtin ' + name +
8827 ' function requires map values to be numbers' +
8828 ' or number arrays';
8829 return new BuiltInError(message);
8830}
8831
8832function sum(values) {
8833 var result = 0;
8834 for (var i = 0, len = values.length; i < len; i++) {
8835 var num = values[i];
8836 if (typeof num !== 'number') {
8837 if (Array.isArray(num)) {
8838 // lists of numbers are also allowed, sum them separately
8839 result = typeof result === 'number' ? [result] : result;
8840 for (var j = 0, jLen = num.length; j < jLen; j++) {
8841 var jNum = num[j];
8842 if (typeof jNum !== 'number') {
8843 throw createBuiltInError('_sum');
8844 } else if (typeof result[j] === 'undefined') {
8845 result.push(jNum);
8846 } else {
8847 result[j] += jNum;
8848 }
8849 }
8850 } else { // not array/number
8851 throw createBuiltInError('_sum');
8852 }
8853 } else if (typeof result === 'number') {
8854 result += num;
8855 } else { // add number to array
8856 result[0] += num;
8857 }
8858 }
8859 return result;
8860}
8861
8862var log = guardedConsole.bind(null, 'log');
8863var isArray = Array.isArray;
8864var toJSON = JSON.parse;
8865
8866function evalFunctionWithEval(func, emit) {
8867 return scopeEval(
8868 "return (" + func.replace(/;\s*$/, "") + ");",
8869 {
8870 emit: emit,
8871 sum: sum,
8872 log: log,
8873 isArray: isArray,
8874 toJSON: toJSON
8875 }
8876 );
8877}
8878
8879/*
8880 * Simple task queue to sequentialize actions. Assumes
8881 * callbacks will eventually fire (once).
8882 */
8883
8884
8885function TaskQueue$1() {
8886 this.promise = new Promise(function (fulfill) {fulfill(); });
8887}
8888TaskQueue$1.prototype.add = function (promiseFactory) {
8889 this.promise = this.promise["catch"](function () {
8890 // just recover
8891 }).then(function () {
8892 return promiseFactory();
8893 });
8894 return this.promise;
8895};
8896TaskQueue$1.prototype.finish = function () {
8897 return this.promise;
8898};
8899
8900function stringify(input) {
8901 if (!input) {
8902 return 'undefined'; // backwards compat for empty reduce
8903 }
8904 // for backwards compat with mapreduce, functions/strings are stringified
8905 // as-is. everything else is JSON-stringified.
8906 switch (typeof input) {
8907 case 'function':
8908 // e.g. a mapreduce map
8909 return input.toString();
8910 case 'string':
8911 // e.g. a mapreduce built-in _reduce function
8912 return input.toString();
8913 default:
8914 // e.g. a JSON object in the case of mango queries
8915 return JSON.stringify(input);
8916 }
8917}
8918
8919/* create a string signature for a view so we can cache it and uniq it */
8920function createViewSignature(mapFun, reduceFun) {
8921 // the "undefined" part is for backwards compatibility
8922 return stringify(mapFun) + stringify(reduceFun) + 'undefined';
8923}
8924
8925function createView(sourceDB, viewName, mapFun, reduceFun, temporary, localDocName) {
8926 var viewSignature = createViewSignature(mapFun, reduceFun);
8927
8928 var cachedViews;
8929 if (!temporary) {
8930 // cache this to ensure we don't try to update the same view twice
8931 cachedViews = sourceDB._cachedViews = sourceDB._cachedViews || {};
8932 if (cachedViews[viewSignature]) {
8933 return cachedViews[viewSignature];
8934 }
8935 }
8936
8937 var promiseForView = sourceDB.info().then(function (info) {
8938
8939 var depDbName = info.db_name + '-mrview-' +
8940 (temporary ? 'temp' : stringMd5(viewSignature));
8941
8942 // save the view name in the source db so it can be cleaned up if necessary
8943 // (e.g. when the _design doc is deleted, remove all associated view data)
8944 function diffFunction(doc) {
8945 doc.views = doc.views || {};
8946 var fullViewName = viewName;
8947 if (fullViewName.indexOf('/') === -1) {
8948 fullViewName = viewName + '/' + viewName;
8949 }
8950 var depDbs = doc.views[fullViewName] = doc.views[fullViewName] || {};
8951 /* istanbul ignore if */
8952 if (depDbs[depDbName]) {
8953 return; // no update necessary
8954 }
8955 depDbs[depDbName] = true;
8956 return doc;
8957 }
8958 return upsert(sourceDB, '_local/' + localDocName, diffFunction).then(function () {
8959 return sourceDB.registerDependentDatabase(depDbName).then(function (res) {
8960 var db = res.db;
8961 db.auto_compaction = true;
8962 var view = {
8963 name: depDbName,
8964 db: db,
8965 sourceDB: sourceDB,
8966 adapter: sourceDB.adapter,
8967 mapFun: mapFun,
8968 reduceFun: reduceFun
8969 };
8970 return view.db.get('_local/lastSeq')["catch"](function (err) {
8971 /* istanbul ignore if */
8972 if (err.status !== 404) {
8973 throw err;
8974 }
8975 }).then(function (lastSeqDoc) {
8976 view.seq = lastSeqDoc ? lastSeqDoc.seq : 0;
8977 if (cachedViews) {
8978 view.db.once('destroyed', function () {
8979 delete cachedViews[viewSignature];
8980 });
8981 }
8982 return view;
8983 });
8984 });
8985 });
8986 });
8987
8988 if (cachedViews) {
8989 cachedViews[viewSignature] = promiseForView;
8990 }
8991 return promiseForView;
8992}
8993
8994var persistentQueues = {};
8995var tempViewQueue = new TaskQueue$1();
8996var CHANGES_BATCH_SIZE$1 = 50;
8997
8998function parseViewName(name) {
8999 // can be either 'ddocname/viewname' or just 'viewname'
9000 // (where the ddoc name is the same)
9001 return name.indexOf('/') === -1 ? [name, name] : name.split('/');
9002}
9003
9004function isGenOne(changes) {
9005 // only return true if the current change is 1-
9006 // and there are no other leafs
9007 return changes.length === 1 && /^1-/.test(changes[0].rev);
9008}
9009
9010function emitError(db, e) {
9011 try {
9012 db.emit('error', e);
9013 } catch (err) {
9014 guardedConsole('error',
9015 'The user\'s map/reduce function threw an uncaught error.\n' +
9016 'You can debug this error by doing:\n' +
9017 'myDatabase.on(\'error\', function (err) { debugger; });\n' +
9018 'Please double-check your map/reduce function.');
9019 guardedConsole('error', e);
9020 }
9021}
9022
9023/**
9024 * Returns an "abstract" mapreduce object of the form:
9025 *
9026 * {
9027 * query: queryFun,
9028 * viewCleanup: viewCleanupFun
9029 * }
9030 *
9031 * Arguments are:
9032 *
9033 * localDoc: string
9034 * This is for the local doc that gets saved in order to track the
9035 * "dependent" DBs and clean them up for viewCleanup. It should be
9036 * unique, so that indexer plugins don't collide with each other.
9037 * mapper: function (mapFunDef, emit)
9038 * Returns a map function based on the mapFunDef, which in the case of
9039 * normal map/reduce is just the de-stringified function, but may be
9040 * something else, such as an object in the case of pouchdb-find.
9041 * reducer: function (reduceFunDef)
9042 * Ditto, but for reducing. Modules don't have to support reducing
9043 * (e.g. pouchdb-find).
9044 * ddocValidator: function (ddoc, viewName)
9045 * Throws an error if the ddoc or viewName is not valid.
9046 * This could be a way to communicate to the user that the configuration for the
9047 * indexer is invalid.
9048 */
9049function createAbstractMapReduce(localDocName, mapper, reducer, ddocValidator) {
9050
9051 function tryMap(db, fun, doc) {
9052 // emit an event if there was an error thrown by a map function.
9053 // putting try/catches in a single function also avoids deoptimizations.
9054 try {
9055 fun(doc);
9056 } catch (e) {
9057 emitError(db, e);
9058 }
9059 }
9060
9061 function tryReduce(db, fun, keys, values, rereduce) {
9062 // same as above, but returning the result or an error. there are two separate
9063 // functions to avoid extra memory allocations since the tryCode() case is used
9064 // for custom map functions (common) vs this function, which is only used for
9065 // custom reduce functions (rare)
9066 try {
9067 return {output : fun(keys, values, rereduce)};
9068 } catch (e) {
9069 emitError(db, e);
9070 return {error: e};
9071 }
9072 }
9073
9074 function sortByKeyThenValue(x, y) {
9075 var keyCompare = collate(x.key, y.key);
9076 return keyCompare !== 0 ? keyCompare : collate(x.value, y.value);
9077 }
9078
9079 function sliceResults(results, limit, skip) {
9080 skip = skip || 0;
9081 if (typeof limit === 'number') {
9082 return results.slice(skip, limit + skip);
9083 } else if (skip > 0) {
9084 return results.slice(skip);
9085 }
9086 return results;
9087 }
9088
9089 function rowToDocId(row) {
9090 var val = row.value;
9091 // Users can explicitly specify a joined doc _id, or it
9092 // defaults to the doc _id that emitted the key/value.
9093 var docId = (val && typeof val === 'object' && val._id) || row.id;
9094 return docId;
9095 }
9096
9097 function readAttachmentsAsBlobOrBuffer(res) {
9098 res.rows.forEach(function (row) {
9099 var atts = row.doc && row.doc._attachments;
9100 if (!atts) {
9101 return;
9102 }
9103 Object.keys(atts).forEach(function (filename) {
9104 var att = atts[filename];
9105 atts[filename].data = b64ToBluffer(att.data, att.content_type);
9106 });
9107 });
9108 }
9109
9110 function postprocessAttachments(opts) {
9111 return function (res) {
9112 if (opts.include_docs && opts.attachments && opts.binary) {
9113 readAttachmentsAsBlobOrBuffer(res);
9114 }
9115 return res;
9116 };
9117 }
9118
9119 function addHttpParam(paramName, opts, params, asJson) {
9120 // add an http param from opts to params, optionally json-encoded
9121 var val = opts[paramName];
9122 if (typeof val !== 'undefined') {
9123 if (asJson) {
9124 val = encodeURIComponent(JSON.stringify(val));
9125 }
9126 params.push(paramName + '=' + val);
9127 }
9128 }
9129
9130 function coerceInteger(integerCandidate) {
9131 if (typeof integerCandidate !== 'undefined') {
9132 var asNumber = Number(integerCandidate);
9133 // prevents e.g. '1foo' or '1.1' being coerced to 1
9134 if (!isNaN(asNumber) && asNumber === parseInt(integerCandidate, 10)) {
9135 return asNumber;
9136 } else {
9137 return integerCandidate;
9138 }
9139 }
9140 }
9141
9142 function coerceOptions(opts) {
9143 opts.group_level = coerceInteger(opts.group_level);
9144 opts.limit = coerceInteger(opts.limit);
9145 opts.skip = coerceInteger(opts.skip);
9146 return opts;
9147 }
9148
9149 function checkPositiveInteger(number) {
9150 if (number) {
9151 if (typeof number !== 'number') {
9152 return new QueryParseError('Invalid value for integer: "' +
9153 number + '"');
9154 }
9155 if (number < 0) {
9156 return new QueryParseError('Invalid value for positive integer: ' +
9157 '"' + number + '"');
9158 }
9159 }
9160 }
9161
9162 function checkQueryParseError(options, fun) {
9163 var startkeyName = options.descending ? 'endkey' : 'startkey';
9164 var endkeyName = options.descending ? 'startkey' : 'endkey';
9165
9166 if (typeof options[startkeyName] !== 'undefined' &&
9167 typeof options[endkeyName] !== 'undefined' &&
9168 collate(options[startkeyName], options[endkeyName]) > 0) {
9169 throw new QueryParseError('No rows can match your key range, ' +
9170 'reverse your start_key and end_key or set {descending : true}');
9171 } else if (fun.reduce && options.reduce !== false) {
9172 if (options.include_docs) {
9173 throw new QueryParseError('{include_docs:true} is invalid for reduce');
9174 } else if (options.keys && options.keys.length > 1 &&
9175 !options.group && !options.group_level) {
9176 throw new QueryParseError('Multi-key fetches for reduce views must use ' +
9177 '{group: true}');
9178 }
9179 }
9180 ['group_level', 'limit', 'skip'].forEach(function (optionName) {
9181 var error = checkPositiveInteger(options[optionName]);
9182 if (error) {
9183 throw error;
9184 }
9185 });
9186 }
9187
9188 function httpQuery(db, fun, opts) {
9189 // List of parameters to add to the PUT request
9190 var params = [];
9191 var body;
9192 var method = 'GET';
9193 var ok, status;
9194
9195 // If opts.reduce exists and is defined, then add it to the list
9196 // of parameters.
9197 // If reduce=false then the results are that of only the map function
9198 // not the final result of map and reduce.
9199 addHttpParam('reduce', opts, params);
9200 addHttpParam('include_docs', opts, params);
9201 addHttpParam('attachments', opts, params);
9202 addHttpParam('limit', opts, params);
9203 addHttpParam('descending', opts, params);
9204 addHttpParam('group', opts, params);
9205 addHttpParam('group_level', opts, params);
9206 addHttpParam('skip', opts, params);
9207 addHttpParam('stale', opts, params);
9208 addHttpParam('conflicts', opts, params);
9209 addHttpParam('startkey', opts, params, true);
9210 addHttpParam('start_key', opts, params, true);
9211 addHttpParam('endkey', opts, params, true);
9212 addHttpParam('end_key', opts, params, true);
9213 addHttpParam('inclusive_end', opts, params);
9214 addHttpParam('key', opts, params, true);
9215 addHttpParam('update_seq', opts, params);
9216
9217 // Format the list of parameters into a valid URI query string
9218 params = params.join('&');
9219 params = params === '' ? '' : '?' + params;
9220
9221 // If keys are supplied, issue a POST to circumvent GET query string limits
9222 // see http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options
9223 if (typeof opts.keys !== 'undefined') {
9224 var MAX_URL_LENGTH = 2000;
9225 // according to http://stackoverflow.com/a/417184/680742,
9226 // the de facto URL length limit is 2000 characters
9227
9228 var keysAsString =
9229 'keys=' + encodeURIComponent(JSON.stringify(opts.keys));
9230 if (keysAsString.length + params.length + 1 <= MAX_URL_LENGTH) {
9231 // If the keys are short enough, do a GET. we do this to work around
9232 // Safari not understanding 304s on POSTs (see pouchdb/pouchdb#1239)
9233 params += (params[0] === '?' ? '&' : '?') + keysAsString;
9234 } else {
9235 method = 'POST';
9236 if (typeof fun === 'string') {
9237 body = {keys: opts.keys};
9238 } else { // fun is {map : mapfun}, so append to this
9239 fun.keys = opts.keys;
9240 }
9241 }
9242 }
9243
9244 // We are referencing a query defined in the design doc
9245 if (typeof fun === 'string') {
9246 var parts = parseViewName(fun);
9247 return db.fetch('_design/' + parts[0] + '/_view/' + parts[1] + params, {
9248 headers: new h({'Content-Type': 'application/json'}),
9249 method: method,
9250 body: JSON.stringify(body)
9251 }).then(function (response) {
9252 ok = response.ok;
9253 status = response.status;
9254 return response.json();
9255 }).then(function (result) {
9256 if (!ok) {
9257 result.status = status;
9258 throw generateErrorFromResponse(result);
9259 }
9260 // fail the entire request if the result contains an error
9261 result.rows.forEach(function (row) {
9262 /* istanbul ignore if */
9263 if (row.value && row.value.error && row.value.error === "builtin_reduce_error") {
9264 throw new Error(row.reason);
9265 }
9266 });
9267 return result;
9268 }).then(postprocessAttachments(opts));
9269 }
9270
9271 // We are using a temporary view, terrible for performance, good for testing
9272 body = body || {};
9273 Object.keys(fun).forEach(function (key) {
9274 if (Array.isArray(fun[key])) {
9275 body[key] = fun[key];
9276 } else {
9277 body[key] = fun[key].toString();
9278 }
9279 });
9280
9281 return db.fetch('_temp_view' + params, {
9282 headers: new h({'Content-Type': 'application/json'}),
9283 method: 'POST',
9284 body: JSON.stringify(body)
9285 }).then(function (response) {
9286 ok = response.ok;
9287 status = response.status;
9288 return response.json();
9289 }).then(function (result) {
9290 if (!ok) {
9291 result.status = status;
9292 throw generateErrorFromResponse(result);
9293 }
9294 return result;
9295 }).then(postprocessAttachments(opts));
9296 }
9297
9298 // custom adapters can define their own api._query
9299 // and override the default behavior
9300 /* istanbul ignore next */
9301 function customQuery(db, fun, opts) {
9302 return new Promise(function (resolve, reject) {
9303 db._query(fun, opts, function (err, res) {
9304 if (err) {
9305 return reject(err);
9306 }
9307 resolve(res);
9308 });
9309 });
9310 }
9311
9312 // custom adapters can define their own api._viewCleanup
9313 // and override the default behavior
9314 /* istanbul ignore next */
9315 function customViewCleanup(db) {
9316 return new Promise(function (resolve, reject) {
9317 db._viewCleanup(function (err, res) {
9318 if (err) {
9319 return reject(err);
9320 }
9321 resolve(res);
9322 });
9323 });
9324 }
9325
9326 function defaultsTo(value) {
9327 return function (reason) {
9328 /* istanbul ignore else */
9329 if (reason.status === 404) {
9330 return value;
9331 } else {
9332 throw reason;
9333 }
9334 };
9335 }
9336
9337 // returns a promise for a list of docs to update, based on the input docId.
9338 // the order doesn't matter, because post-3.2.0, bulkDocs
9339 // is an atomic operation in all three adapters.
9340 function getDocsToPersist(docId, view, docIdsToChangesAndEmits) {
9341 var metaDocId = '_local/doc_' + docId;
9342 var defaultMetaDoc = {_id: metaDocId, keys: []};
9343 var docData = docIdsToChangesAndEmits.get(docId);
9344 var indexableKeysToKeyValues = docData[0];
9345 var changes = docData[1];
9346
9347 function getMetaDoc() {
9348 if (isGenOne(changes)) {
9349 // generation 1, so we can safely assume initial state
9350 // for performance reasons (avoids unnecessary GETs)
9351 return Promise.resolve(defaultMetaDoc);
9352 }
9353 return view.db.get(metaDocId)["catch"](defaultsTo(defaultMetaDoc));
9354 }
9355
9356 function getKeyValueDocs(metaDoc) {
9357 if (!metaDoc.keys.length) {
9358 // no keys, no need for a lookup
9359 return Promise.resolve({rows: []});
9360 }
9361 return view.db.allDocs({
9362 keys: metaDoc.keys,
9363 include_docs: true
9364 });
9365 }
9366
9367 function processKeyValueDocs(metaDoc, kvDocsRes) {
9368 var kvDocs = [];
9369 var oldKeys = new ExportedSet();
9370
9371 for (var i = 0, len = kvDocsRes.rows.length; i < len; i++) {
9372 var row = kvDocsRes.rows[i];
9373 var doc = row.doc;
9374 if (!doc) { // deleted
9375 continue;
9376 }
9377 kvDocs.push(doc);
9378 oldKeys.add(doc._id);
9379 doc._deleted = !indexableKeysToKeyValues.has(doc._id);
9380 if (!doc._deleted) {
9381 var keyValue = indexableKeysToKeyValues.get(doc._id);
9382 if ('value' in keyValue) {
9383 doc.value = keyValue.value;
9384 }
9385 }
9386 }
9387 var newKeys = mapToKeysArray(indexableKeysToKeyValues);
9388 newKeys.forEach(function (key) {
9389 if (!oldKeys.has(key)) {
9390 // new doc
9391 var kvDoc = {
9392 _id: key
9393 };
9394 var keyValue = indexableKeysToKeyValues.get(key);
9395 if ('value' in keyValue) {
9396 kvDoc.value = keyValue.value;
9397 }
9398 kvDocs.push(kvDoc);
9399 }
9400 });
9401 metaDoc.keys = uniq(newKeys.concat(metaDoc.keys));
9402 kvDocs.push(metaDoc);
9403
9404 return kvDocs;
9405 }
9406
9407 return getMetaDoc().then(function (metaDoc) {
9408 return getKeyValueDocs(metaDoc).then(function (kvDocsRes) {
9409 return processKeyValueDocs(metaDoc, kvDocsRes);
9410 });
9411 });
9412 }
9413
9414 // updates all emitted key/value docs and metaDocs in the mrview database
9415 // for the given batch of documents from the source database
9416 function saveKeyValues(view, docIdsToChangesAndEmits, seq) {
9417 var seqDocId = '_local/lastSeq';
9418 return view.db.get(seqDocId)[
9419 "catch"](defaultsTo({_id: seqDocId, seq: 0}))
9420 .then(function (lastSeqDoc) {
9421 var docIds = mapToKeysArray(docIdsToChangesAndEmits);
9422 return Promise.all(docIds.map(function (docId) {
9423 return getDocsToPersist(docId, view, docIdsToChangesAndEmits);
9424 })).then(function (listOfDocsToPersist) {
9425 var docsToPersist = flatten(listOfDocsToPersist);
9426 lastSeqDoc.seq = seq;
9427 docsToPersist.push(lastSeqDoc);
9428 // write all docs in a single operation, update the seq once
9429 return view.db.bulkDocs({docs : docsToPersist});
9430 });
9431 });
9432 }
9433
9434 function getQueue(view) {
9435 var viewName = typeof view === 'string' ? view : view.name;
9436 var queue = persistentQueues[viewName];
9437 if (!queue) {
9438 queue = persistentQueues[viewName] = new TaskQueue$1();
9439 }
9440 return queue;
9441 }
9442
9443 function updateView(view) {
9444 return sequentialize(getQueue(view), function () {
9445 return updateViewInQueue(view);
9446 })();
9447 }
9448
9449 function updateViewInQueue(view) {
9450 // bind the emit function once
9451 var mapResults;
9452 var doc;
9453
9454 function emit(key, value) {
9455 var output = {id: doc._id, key: normalizeKey(key)};
9456 // Don't explicitly store the value unless it's defined and non-null.
9457 // This saves on storage space, because often people don't use it.
9458 if (typeof value !== 'undefined' && value !== null) {
9459 output.value = normalizeKey(value);
9460 }
9461 mapResults.push(output);
9462 }
9463
9464 var mapFun = mapper(view.mapFun, emit);
9465
9466 var currentSeq = view.seq || 0;
9467
9468 function processChange(docIdsToChangesAndEmits, seq) {
9469 return function () {
9470 return saveKeyValues(view, docIdsToChangesAndEmits, seq);
9471 };
9472 }
9473
9474 var queue = new TaskQueue$1();
9475
9476 function processNextBatch() {
9477 return view.sourceDB.changes({
9478 return_docs: true,
9479 conflicts: true,
9480 include_docs: true,
9481 style: 'all_docs',
9482 since: currentSeq,
9483 limit: CHANGES_BATCH_SIZE$1
9484 }).then(processBatch);
9485 }
9486
9487 function processBatch(response) {
9488 var results = response.results;
9489 if (!results.length) {
9490 return;
9491 }
9492 var docIdsToChangesAndEmits = createDocIdsToChangesAndEmits(results);
9493 queue.add(processChange(docIdsToChangesAndEmits, currentSeq));
9494 if (results.length < CHANGES_BATCH_SIZE$1) {
9495 return;
9496 }
9497 return processNextBatch();
9498 }
9499
9500 function createDocIdsToChangesAndEmits(results) {
9501 var docIdsToChangesAndEmits = new ExportedMap();
9502 for (var i = 0, len = results.length; i < len; i++) {
9503 var change = results[i];
9504 if (change.doc._id[0] !== '_') {
9505 mapResults = [];
9506 doc = change.doc;
9507
9508 if (!doc._deleted) {
9509 tryMap(view.sourceDB, mapFun, doc);
9510 }
9511 mapResults.sort(sortByKeyThenValue);
9512
9513 var indexableKeysToKeyValues = createIndexableKeysToKeyValues(mapResults);
9514 docIdsToChangesAndEmits.set(change.doc._id, [
9515 indexableKeysToKeyValues,
9516 change.changes
9517 ]);
9518 }
9519 currentSeq = change.seq;
9520 }
9521 return docIdsToChangesAndEmits;
9522 }
9523
9524 function createIndexableKeysToKeyValues(mapResults) {
9525 var indexableKeysToKeyValues = new ExportedMap();
9526 var lastKey;
9527 for (var i = 0, len = mapResults.length; i < len; i++) {
9528 var emittedKeyValue = mapResults[i];
9529 var complexKey = [emittedKeyValue.key, emittedKeyValue.id];
9530 if (i > 0 && collate(emittedKeyValue.key, lastKey) === 0) {
9531 complexKey.push(i); // dup key+id, so make it unique
9532 }
9533 indexableKeysToKeyValues.set(toIndexableString(complexKey), emittedKeyValue);
9534 lastKey = emittedKeyValue.key;
9535 }
9536 return indexableKeysToKeyValues;
9537 }
9538
9539 return processNextBatch().then(function () {
9540 return queue.finish();
9541 }).then(function () {
9542 view.seq = currentSeq;
9543 });
9544 }
9545
9546 function reduceView(view, results, options) {
9547 if (options.group_level === 0) {
9548 delete options.group_level;
9549 }
9550
9551 var shouldGroup = options.group || options.group_level;
9552
9553 var reduceFun = reducer(view.reduceFun);
9554
9555 var groups = [];
9556 var lvl = isNaN(options.group_level) ? Number.POSITIVE_INFINITY :
9557 options.group_level;
9558 results.forEach(function (e) {
9559 var last = groups[groups.length - 1];
9560 var groupKey = shouldGroup ? e.key : null;
9561
9562 // only set group_level for array keys
9563 if (shouldGroup && Array.isArray(groupKey)) {
9564 groupKey = groupKey.slice(0, lvl);
9565 }
9566
9567 if (last && collate(last.groupKey, groupKey) === 0) {
9568 last.keys.push([e.key, e.id]);
9569 last.values.push(e.value);
9570 return;
9571 }
9572 groups.push({
9573 keys: [[e.key, e.id]],
9574 values: [e.value],
9575 groupKey: groupKey
9576 });
9577 });
9578 results = [];
9579 for (var i = 0, len = groups.length; i < len; i++) {
9580 var e = groups[i];
9581 var reduceTry = tryReduce(view.sourceDB, reduceFun, e.keys, e.values, false);
9582 if (reduceTry.error && reduceTry.error instanceof BuiltInError) {
9583 // CouchDB returns an error if a built-in errors out
9584 throw reduceTry.error;
9585 }
9586 results.push({
9587 // CouchDB just sets the value to null if a non-built-in errors out
9588 value: reduceTry.error ? null : reduceTry.output,
9589 key: e.groupKey
9590 });
9591 }
9592 // no total_rows/offset when reducing
9593 return {rows: sliceResults(results, options.limit, options.skip)};
9594 }
9595
9596 function queryView(view, opts) {
9597 return sequentialize(getQueue(view), function () {
9598 return queryViewInQueue(view, opts);
9599 })();
9600 }
9601
9602 function queryViewInQueue(view, opts) {
9603 var totalRows;
9604 var shouldReduce = view.reduceFun && opts.reduce !== false;
9605 var skip = opts.skip || 0;
9606 if (typeof opts.keys !== 'undefined' && !opts.keys.length) {
9607 // equivalent query
9608 opts.limit = 0;
9609 delete opts.keys;
9610 }
9611
9612 function fetchFromView(viewOpts) {
9613 viewOpts.include_docs = true;
9614 return view.db.allDocs(viewOpts).then(function (res) {
9615 totalRows = res.total_rows;
9616 return res.rows.map(function (result) {
9617
9618 // implicit migration - in older versions of PouchDB,
9619 // we explicitly stored the doc as {id: ..., key: ..., value: ...}
9620 // this is tested in a migration test
9621 /* istanbul ignore next */
9622 if ('value' in result.doc && typeof result.doc.value === 'object' &&
9623 result.doc.value !== null) {
9624 var keys = Object.keys(result.doc.value).sort();
9625 // this detection method is not perfect, but it's unlikely the user
9626 // emitted a value which was an object with these 3 exact keys
9627 var expectedKeys = ['id', 'key', 'value'];
9628 if (!(keys < expectedKeys || keys > expectedKeys)) {
9629 return result.doc.value;
9630 }
9631 }
9632
9633 var parsedKeyAndDocId = parseIndexableString(result.doc._id);
9634 return {
9635 key: parsedKeyAndDocId[0],
9636 id: parsedKeyAndDocId[1],
9637 value: ('value' in result.doc ? result.doc.value : null)
9638 };
9639 });
9640 });
9641 }
9642
9643 function onMapResultsReady(rows) {
9644 var finalResults;
9645 if (shouldReduce) {
9646 finalResults = reduceView(view, rows, opts);
9647 } else {
9648 finalResults = {
9649 total_rows: totalRows,
9650 offset: skip,
9651 rows: rows
9652 };
9653 }
9654 /* istanbul ignore if */
9655 if (opts.update_seq) {
9656 finalResults.update_seq = view.seq;
9657 }
9658 if (opts.include_docs) {
9659 var docIds = uniq(rows.map(rowToDocId));
9660
9661 return view.sourceDB.allDocs({
9662 keys: docIds,
9663 include_docs: true,
9664 conflicts: opts.conflicts,
9665 attachments: opts.attachments,
9666 binary: opts.binary
9667 }).then(function (allDocsRes) {
9668 var docIdsToDocs = new ExportedMap();
9669 allDocsRes.rows.forEach(function (row) {
9670 docIdsToDocs.set(row.id, row.doc);
9671 });
9672 rows.forEach(function (row) {
9673 var docId = rowToDocId(row);
9674 var doc = docIdsToDocs.get(docId);
9675 if (doc) {
9676 row.doc = doc;
9677 }
9678 });
9679 return finalResults;
9680 });
9681 } else {
9682 return finalResults;
9683 }
9684 }
9685
9686 if (typeof opts.keys !== 'undefined') {
9687 var keys = opts.keys;
9688 var fetchPromises = keys.map(function (key) {
9689 var viewOpts = {
9690 startkey : toIndexableString([key]),
9691 endkey : toIndexableString([key, {}])
9692 };
9693 /* istanbul ignore if */
9694 if (opts.update_seq) {
9695 viewOpts.update_seq = true;
9696 }
9697 return fetchFromView(viewOpts);
9698 });
9699 return Promise.all(fetchPromises).then(flatten).then(onMapResultsReady);
9700 } else { // normal query, no 'keys'
9701 var viewOpts = {
9702 descending : opts.descending
9703 };
9704 /* istanbul ignore if */
9705 if (opts.update_seq) {
9706 viewOpts.update_seq = true;
9707 }
9708 var startkey;
9709 var endkey;
9710 if ('start_key' in opts) {
9711 startkey = opts.start_key;
9712 }
9713 if ('startkey' in opts) {
9714 startkey = opts.startkey;
9715 }
9716 if ('end_key' in opts) {
9717 endkey = opts.end_key;
9718 }
9719 if ('endkey' in opts) {
9720 endkey = opts.endkey;
9721 }
9722 if (typeof startkey !== 'undefined') {
9723 viewOpts.startkey = opts.descending ?
9724 toIndexableString([startkey, {}]) :
9725 toIndexableString([startkey]);
9726 }
9727 if (typeof endkey !== 'undefined') {
9728 var inclusiveEnd = opts.inclusive_end !== false;
9729 if (opts.descending) {
9730 inclusiveEnd = !inclusiveEnd;
9731 }
9732
9733 viewOpts.endkey = toIndexableString(
9734 inclusiveEnd ? [endkey, {}] : [endkey]);
9735 }
9736 if (typeof opts.key !== 'undefined') {
9737 var keyStart = toIndexableString([opts.key]);
9738 var keyEnd = toIndexableString([opts.key, {}]);
9739 if (viewOpts.descending) {
9740 viewOpts.endkey = keyStart;
9741 viewOpts.startkey = keyEnd;
9742 } else {
9743 viewOpts.startkey = keyStart;
9744 viewOpts.endkey = keyEnd;
9745 }
9746 }
9747 if (!shouldReduce) {
9748 if (typeof opts.limit === 'number') {
9749 viewOpts.limit = opts.limit;
9750 }
9751 viewOpts.skip = skip;
9752 }
9753 return fetchFromView(viewOpts).then(onMapResultsReady);
9754 }
9755 }
9756
9757 function httpViewCleanup(db) {
9758 return db.fetch('_view_cleanup', {
9759 headers: new h({'Content-Type': 'application/json'}),
9760 method: 'POST'
9761 }).then(function (response) {
9762 return response.json();
9763 });
9764 }
9765
9766 function localViewCleanup(db) {
9767 return db.get('_local/' + localDocName).then(function (metaDoc) {
9768 var docsToViews = new ExportedMap();
9769 Object.keys(metaDoc.views).forEach(function (fullViewName) {
9770 var parts = parseViewName(fullViewName);
9771 var designDocName = '_design/' + parts[0];
9772 var viewName = parts[1];
9773 var views = docsToViews.get(designDocName);
9774 if (!views) {
9775 views = new ExportedSet();
9776 docsToViews.set(designDocName, views);
9777 }
9778 views.add(viewName);
9779 });
9780 var opts = {
9781 keys : mapToKeysArray(docsToViews),
9782 include_docs : true
9783 };
9784 return db.allDocs(opts).then(function (res) {
9785 var viewsToStatus = {};
9786 res.rows.forEach(function (row) {
9787 var ddocName = row.key.substring(8); // cuts off '_design/'
9788 docsToViews.get(row.key).forEach(function (viewName) {
9789 var fullViewName = ddocName + '/' + viewName;
9790 /* istanbul ignore if */
9791 if (!metaDoc.views[fullViewName]) {
9792 // new format, without slashes, to support PouchDB 2.2.0
9793 // migration test in pouchdb's browser.migration.js verifies this
9794 fullViewName = viewName;
9795 }
9796 var viewDBNames = Object.keys(metaDoc.views[fullViewName]);
9797 // design doc deleted, or view function nonexistent
9798 var statusIsGood = row.doc && row.doc.views &&
9799 row.doc.views[viewName];
9800 viewDBNames.forEach(function (viewDBName) {
9801 viewsToStatus[viewDBName] =
9802 viewsToStatus[viewDBName] || statusIsGood;
9803 });
9804 });
9805 });
9806 var dbsToDelete = Object.keys(viewsToStatus).filter(
9807 function (viewDBName) { return !viewsToStatus[viewDBName]; });
9808 var destroyPromises = dbsToDelete.map(function (viewDBName) {
9809 return sequentialize(getQueue(viewDBName), function () {
9810 return new db.constructor(viewDBName, db.__opts).destroy();
9811 })();
9812 });
9813 return Promise.all(destroyPromises).then(function () {
9814 return {ok: true};
9815 });
9816 });
9817 }, defaultsTo({ok: true}));
9818 }
9819
9820 function queryPromised(db, fun, opts) {
9821 /* istanbul ignore next */
9822 if (typeof db._query === 'function') {
9823 return customQuery(db, fun, opts);
9824 }
9825 if (isRemote(db)) {
9826 return httpQuery(db, fun, opts);
9827 }
9828
9829 if (typeof fun !== 'string') {
9830 // temp_view
9831 checkQueryParseError(opts, fun);
9832
9833 tempViewQueue.add(function () {
9834 var createViewPromise = createView(
9835 /* sourceDB */ db,
9836 /* viewName */ 'temp_view/temp_view',
9837 /* mapFun */ fun.map,
9838 /* reduceFun */ fun.reduce,
9839 /* temporary */ true,
9840 /* localDocName */ localDocName);
9841 return createViewPromise.then(function (view) {
9842 return fin(updateView(view).then(function () {
9843 return queryView(view, opts);
9844 }), function () {
9845 return view.db.destroy();
9846 });
9847 });
9848 });
9849 return tempViewQueue.finish();
9850 } else {
9851 // persistent view
9852 var fullViewName = fun;
9853 var parts = parseViewName(fullViewName);
9854 var designDocName = parts[0];
9855 var viewName = parts[1];
9856 return db.get('_design/' + designDocName).then(function (doc) {
9857 var fun = doc.views && doc.views[viewName];
9858
9859 if (!fun) {
9860 // basic validator; it's assumed that every subclass would want this
9861 throw new NotFoundError('ddoc ' + doc._id + ' has no view named ' +
9862 viewName);
9863 }
9864
9865 ddocValidator(doc, viewName);
9866 checkQueryParseError(opts, fun);
9867
9868 var createViewPromise = createView(
9869 /* sourceDB */ db,
9870 /* viewName */ fullViewName,
9871 /* mapFun */ fun.map,
9872 /* reduceFun */ fun.reduce,
9873 /* temporary */ false,
9874 /* localDocName */ localDocName);
9875 return createViewPromise.then(function (view) {
9876 if (opts.stale === 'ok' || opts.stale === 'update_after') {
9877 if (opts.stale === 'update_after') {
9878 immediate(function () {
9879 updateView(view);
9880 });
9881 }
9882 return queryView(view, opts);
9883 } else { // stale not ok
9884 return updateView(view).then(function () {
9885 return queryView(view, opts);
9886 });
9887 }
9888 });
9889 });
9890 }
9891 }
9892
9893 function abstractQuery(fun, opts, callback) {
9894 var db = this;
9895 if (typeof opts === 'function') {
9896 callback = opts;
9897 opts = {};
9898 }
9899 opts = opts ? coerceOptions(opts) : {};
9900
9901 if (typeof fun === 'function') {
9902 fun = {map : fun};
9903 }
9904
9905 var promise = Promise.resolve().then(function () {
9906 return queryPromised(db, fun, opts);
9907 });
9908 promisedCallback(promise, callback);
9909 return promise;
9910 }
9911
9912 var abstractViewCleanup = callbackify(function () {
9913 var db = this;
9914 /* istanbul ignore next */
9915 if (typeof db._viewCleanup === 'function') {
9916 return customViewCleanup(db);
9917 }
9918 if (isRemote(db)) {
9919 return httpViewCleanup(db);
9920 }
9921 return localViewCleanup(db);
9922 });
9923
9924 return {
9925 query: abstractQuery,
9926 viewCleanup: abstractViewCleanup
9927 };
9928}
9929
9930var builtInReduce = {
9931 _sum: function (keys, values) {
9932 return sum(values);
9933 },
9934
9935 _count: function (keys, values) {
9936 return values.length;
9937 },
9938
9939 _stats: function (keys, values) {
9940 // no need to implement rereduce=true, because Pouch
9941 // will never call it
9942 function sumsqr(values) {
9943 var _sumsqr = 0;
9944 for (var i = 0, len = values.length; i < len; i++) {
9945 var num = values[i];
9946 _sumsqr += (num * num);
9947 }
9948 return _sumsqr;
9949 }
9950 return {
9951 sum : sum(values),
9952 min : Math.min.apply(null, values),
9953 max : Math.max.apply(null, values),
9954 count : values.length,
9955 sumsqr : sumsqr(values)
9956 };
9957 }
9958};
9959
9960function getBuiltIn(reduceFunString) {
9961 if (/^_sum/.test(reduceFunString)) {
9962 return builtInReduce._sum;
9963 } else if (/^_count/.test(reduceFunString)) {
9964 return builtInReduce._count;
9965 } else if (/^_stats/.test(reduceFunString)) {
9966 return builtInReduce._stats;
9967 } else if (/^_/.test(reduceFunString)) {
9968 throw new Error(reduceFunString + ' is not a supported reduce function.');
9969 }
9970}
9971
9972function mapper(mapFun, emit) {
9973 // for temp_views one can use emit(doc, emit), see #38
9974 if (typeof mapFun === "function" && mapFun.length === 2) {
9975 var origMap = mapFun;
9976 return function (doc) {
9977 return origMap(doc, emit);
9978 };
9979 } else {
9980 return evalFunctionWithEval(mapFun.toString(), emit);
9981 }
9982}
9983
9984function reducer(reduceFun) {
9985 var reduceFunString = reduceFun.toString();
9986 var builtIn = getBuiltIn(reduceFunString);
9987 if (builtIn) {
9988 return builtIn;
9989 } else {
9990 return evalFunctionWithEval(reduceFunString);
9991 }
9992}
9993
9994function ddocValidator(ddoc, viewName) {
9995 var fun = ddoc.views && ddoc.views[viewName];
9996 if (typeof fun.map !== 'string') {
9997 throw new NotFoundError('ddoc ' + ddoc._id + ' has no string view named ' +
9998 viewName + ', instead found object of type: ' + typeof fun.map);
9999 }
10000}
10001
10002var localDocName = 'mrviews';
10003var abstract = createAbstractMapReduce(localDocName, mapper, reducer, ddocValidator);
10004
10005function query$1(fun, opts, callback) {
10006 return abstract.query.call(this, fun, opts, callback);
10007}
10008
10009function viewCleanup$1(callback) {
10010 return abstract.viewCleanup.call(this, callback);
10011}
10012
10013var mapreduce = {
10014 query: query$1,
10015 viewCleanup: viewCleanup$1
10016};
10017
10018function isGenOne$1(rev$$1) {
10019 return /^1-/.test(rev$$1);
10020}
10021
10022function fileHasChanged(localDoc, remoteDoc, filename) {
10023 return !localDoc._attachments ||
10024 !localDoc._attachments[filename] ||
10025 localDoc._attachments[filename].digest !== remoteDoc._attachments[filename].digest;
10026}
10027
10028function getDocAttachments(db, doc) {
10029 var filenames = Object.keys(doc._attachments);
10030 return Promise.all(filenames.map(function (filename) {
10031 return db.getAttachment(doc._id, filename, {rev: doc._rev});
10032 }));
10033}
10034
10035function getDocAttachmentsFromTargetOrSource(target, src, doc) {
10036 var doCheckForLocalAttachments = isRemote(src) && !isRemote(target);
10037 var filenames = Object.keys(doc._attachments);
10038
10039 if (!doCheckForLocalAttachments) {
10040 return getDocAttachments(src, doc);
10041 }
10042
10043 return target.get(doc._id).then(function (localDoc) {
10044 return Promise.all(filenames.map(function (filename) {
10045 if (fileHasChanged(localDoc, doc, filename)) {
10046 return src.getAttachment(doc._id, filename);
10047 }
10048
10049 return target.getAttachment(localDoc._id, filename);
10050 }));
10051 })["catch"](function (error) {
10052 /* istanbul ignore if */
10053 if (error.status !== 404) {
10054 throw error;
10055 }
10056
10057 return getDocAttachments(src, doc);
10058 });
10059}
10060
10061function createBulkGetOpts(diffs) {
10062 var requests = [];
10063 Object.keys(diffs).forEach(function (id) {
10064 var missingRevs = diffs[id].missing;
10065 missingRevs.forEach(function (missingRev) {
10066 requests.push({
10067 id: id,
10068 rev: missingRev
10069 });
10070 });
10071 });
10072
10073 return {
10074 docs: requests,
10075 revs: true,
10076 latest: true
10077 };
10078}
10079
10080//
10081// Fetch all the documents from the src as described in the "diffs",
10082// which is a mapping of docs IDs to revisions. If the state ever
10083// changes to "cancelled", then the returned promise will be rejected.
10084// Else it will be resolved with a list of fetched documents.
10085//
10086function getDocs(src, target, diffs, state) {
10087 diffs = clone(diffs); // we do not need to modify this
10088
10089 var resultDocs = [],
10090 ok = true;
10091
10092 function getAllDocs() {
10093
10094 var bulkGetOpts = createBulkGetOpts(diffs);
10095
10096 if (!bulkGetOpts.docs.length) { // optimization: skip empty requests
10097 return;
10098 }
10099
10100 return src.bulkGet(bulkGetOpts).then(function (bulkGetResponse) {
10101 /* istanbul ignore if */
10102 if (state.cancelled) {
10103 throw new Error('cancelled');
10104 }
10105 return Promise.all(bulkGetResponse.results.map(function (bulkGetInfo) {
10106 return Promise.all(bulkGetInfo.docs.map(function (doc) {
10107 var remoteDoc = doc.ok;
10108
10109 if (doc.error) {
10110 // when AUTO_COMPACTION is set, docs can be returned which look
10111 // like this: {"missing":"1-7c3ac256b693c462af8442f992b83696"}
10112 ok = false;
10113 }
10114
10115 if (!remoteDoc || !remoteDoc._attachments) {
10116 return remoteDoc;
10117 }
10118
10119 return getDocAttachmentsFromTargetOrSource(target, src, remoteDoc)
10120 .then(function (attachments) {
10121 var filenames = Object.keys(remoteDoc._attachments);
10122 attachments
10123 .forEach(function (attachment, i) {
10124 var att = remoteDoc._attachments[filenames[i]];
10125 delete att.stub;
10126 delete att.length;
10127 att.data = attachment;
10128 });
10129
10130 return remoteDoc;
10131 });
10132 }));
10133 }))
10134
10135 .then(function (results) {
10136 resultDocs = resultDocs.concat(flatten(results).filter(Boolean));
10137 });
10138 });
10139 }
10140
10141 function hasAttachments(doc) {
10142 return doc._attachments && Object.keys(doc._attachments).length > 0;
10143 }
10144
10145 function hasConflicts(doc) {
10146 return doc._conflicts && doc._conflicts.length > 0;
10147 }
10148
10149 function fetchRevisionOneDocs(ids) {
10150 // Optimization: fetch gen-1 docs and attachments in
10151 // a single request using _all_docs
10152 return src.allDocs({
10153 keys: ids,
10154 include_docs: true,
10155 conflicts: true
10156 }).then(function (res) {
10157 if (state.cancelled) {
10158 throw new Error('cancelled');
10159 }
10160 res.rows.forEach(function (row) {
10161 if (row.deleted || !row.doc || !isGenOne$1(row.value.rev) ||
10162 hasAttachments(row.doc) || hasConflicts(row.doc)) {
10163 // if any of these conditions apply, we need to fetch using get()
10164 return;
10165 }
10166
10167 // strip _conflicts array to appease CSG (#5793)
10168 /* istanbul ignore if */
10169 if (row.doc._conflicts) {
10170 delete row.doc._conflicts;
10171 }
10172
10173 // the doc we got back from allDocs() is sufficient
10174 resultDocs.push(row.doc);
10175 delete diffs[row.id];
10176 });
10177 });
10178 }
10179
10180 function getRevisionOneDocs() {
10181 // filter out the generation 1 docs and get them
10182 // leaving the non-generation one docs to be got otherwise
10183 var ids = Object.keys(diffs).filter(function (id) {
10184 var missing = diffs[id].missing;
10185 return missing.length === 1 && isGenOne$1(missing[0]);
10186 });
10187 if (ids.length > 0) {
10188 return fetchRevisionOneDocs(ids);
10189 }
10190 }
10191
10192 function returnResult() {
10193 return { ok:ok, docs:resultDocs };
10194 }
10195
10196 return Promise.resolve()
10197 .then(getRevisionOneDocs)
10198 .then(getAllDocs)
10199 .then(returnResult);
10200}
10201
10202var CHECKPOINT_VERSION = 1;
10203var REPLICATOR = "pouchdb";
10204// This is an arbitrary number to limit the
10205// amount of replication history we save in the checkpoint.
10206// If we save too much, the checkpoing docs will become very big,
10207// if we save fewer, we'll run a greater risk of having to
10208// read all the changes from 0 when checkpoint PUTs fail
10209// CouchDB 2.0 has a more involved history pruning,
10210// but let's go for the simple version for now.
10211var CHECKPOINT_HISTORY_SIZE = 5;
10212var LOWEST_SEQ = 0;
10213
10214function updateCheckpoint(db, id, checkpoint, session, returnValue) {
10215 return db.get(id)["catch"](function (err) {
10216 if (err.status === 404) {
10217 if (db.adapter === 'http' || db.adapter === 'https') {
10218 explainError(
10219 404, 'PouchDB is just checking if a remote checkpoint exists.'
10220 );
10221 }
10222 return {
10223 session_id: session,
10224 _id: id,
10225 history: [],
10226 replicator: REPLICATOR,
10227 version: CHECKPOINT_VERSION
10228 };
10229 }
10230 throw err;
10231 }).then(function (doc) {
10232 if (returnValue.cancelled) {
10233 return;
10234 }
10235
10236 // if the checkpoint has not changed, do not update
10237 if (doc.last_seq === checkpoint) {
10238 return;
10239 }
10240
10241 // Filter out current entry for this replication
10242 doc.history = (doc.history || []).filter(function (item) {
10243 return item.session_id !== session;
10244 });
10245
10246 // Add the latest checkpoint to history
10247 doc.history.unshift({
10248 last_seq: checkpoint,
10249 session_id: session
10250 });
10251
10252 // Just take the last pieces in history, to
10253 // avoid really big checkpoint docs.
10254 // see comment on history size above
10255 doc.history = doc.history.slice(0, CHECKPOINT_HISTORY_SIZE);
10256
10257 doc.version = CHECKPOINT_VERSION;
10258 doc.replicator = REPLICATOR;
10259
10260 doc.session_id = session;
10261 doc.last_seq = checkpoint;
10262
10263 return db.put(doc)["catch"](function (err) {
10264 if (err.status === 409) {
10265 // retry; someone is trying to write a checkpoint simultaneously
10266 return updateCheckpoint(db, id, checkpoint, session, returnValue);
10267 }
10268 throw err;
10269 });
10270 });
10271}
10272
10273function Checkpointer(src, target, id, returnValue, opts) {
10274 this.src = src;
10275 this.target = target;
10276 this.id = id;
10277 this.returnValue = returnValue;
10278 this.opts = opts || {};
10279}
10280
10281Checkpointer.prototype.writeCheckpoint = function (checkpoint, session) {
10282 var self = this;
10283 return this.updateTarget(checkpoint, session).then(function () {
10284 return self.updateSource(checkpoint, session);
10285 });
10286};
10287
10288Checkpointer.prototype.updateTarget = function (checkpoint, session) {
10289 if (this.opts.writeTargetCheckpoint) {
10290 return updateCheckpoint(this.target, this.id, checkpoint,
10291 session, this.returnValue);
10292 } else {
10293 return Promise.resolve(true);
10294 }
10295};
10296
10297Checkpointer.prototype.updateSource = function (checkpoint, session) {
10298 if (this.opts.writeSourceCheckpoint) {
10299 var self = this;
10300 return updateCheckpoint(this.src, this.id, checkpoint,
10301 session, this.returnValue)[
10302 "catch"](function (err) {
10303 if (isForbiddenError(err)) {
10304 self.opts.writeSourceCheckpoint = false;
10305 return true;
10306 }
10307 throw err;
10308 });
10309 } else {
10310 return Promise.resolve(true);
10311 }
10312};
10313
10314var comparisons = {
10315 "undefined": function (targetDoc, sourceDoc) {
10316 // This is the previous comparison function
10317 if (collate(targetDoc.last_seq, sourceDoc.last_seq) === 0) {
10318 return sourceDoc.last_seq;
10319 }
10320 /* istanbul ignore next */
10321 return 0;
10322 },
10323 "1": function (targetDoc, sourceDoc) {
10324 // This is the comparison function ported from CouchDB
10325 return compareReplicationLogs(sourceDoc, targetDoc).last_seq;
10326 }
10327};
10328
10329Checkpointer.prototype.getCheckpoint = function () {
10330 var self = this;
10331
10332 if (self.opts && self.opts.writeSourceCheckpoint && !self.opts.writeTargetCheckpoint) {
10333 return self.src.get(self.id).then(function (sourceDoc) {
10334 return sourceDoc.last_seq || LOWEST_SEQ;
10335 })["catch"](function (err) {
10336 /* istanbul ignore if */
10337 if (err.status !== 404) {
10338 throw err;
10339 }
10340 return LOWEST_SEQ;
10341 });
10342 }
10343
10344 return self.target.get(self.id).then(function (targetDoc) {
10345 if (self.opts && self.opts.writeTargetCheckpoint && !self.opts.writeSourceCheckpoint) {
10346 return targetDoc.last_seq || LOWEST_SEQ;
10347 }
10348
10349 return self.src.get(self.id).then(function (sourceDoc) {
10350 // Since we can't migrate an old version doc to a new one
10351 // (no session id), we just go with the lowest seq in this case
10352 /* istanbul ignore if */
10353 if (targetDoc.version !== sourceDoc.version) {
10354 return LOWEST_SEQ;
10355 }
10356
10357 var version;
10358 if (targetDoc.version) {
10359 version = targetDoc.version.toString();
10360 } else {
10361 version = "undefined";
10362 }
10363
10364 if (version in comparisons) {
10365 return comparisons[version](targetDoc, sourceDoc);
10366 }
10367 /* istanbul ignore next */
10368 return LOWEST_SEQ;
10369 }, function (err) {
10370 if (err.status === 404 && targetDoc.last_seq) {
10371 return self.src.put({
10372 _id: self.id,
10373 last_seq: LOWEST_SEQ
10374 }).then(function () {
10375 return LOWEST_SEQ;
10376 }, function (err) {
10377 if (isForbiddenError(err)) {
10378 self.opts.writeSourceCheckpoint = false;
10379 return targetDoc.last_seq;
10380 }
10381 /* istanbul ignore next */
10382 return LOWEST_SEQ;
10383 });
10384 }
10385 throw err;
10386 });
10387 })["catch"](function (err) {
10388 if (err.status !== 404) {
10389 throw err;
10390 }
10391 return LOWEST_SEQ;
10392 });
10393};
10394// This checkpoint comparison is ported from CouchDBs source
10395// they come from here:
10396// https://github.com/apache/couchdb-couch-replicator/blob/master/src/couch_replicator.erl#L863-L906
10397
10398function compareReplicationLogs(srcDoc, tgtDoc) {
10399 if (srcDoc.session_id === tgtDoc.session_id) {
10400 return {
10401 last_seq: srcDoc.last_seq,
10402 history: srcDoc.history
10403 };
10404 }
10405
10406 return compareReplicationHistory(srcDoc.history, tgtDoc.history);
10407}
10408
10409function compareReplicationHistory(sourceHistory, targetHistory) {
10410 // the erlang loop via function arguments is not so easy to repeat in JS
10411 // therefore, doing this as recursion
10412 var S = sourceHistory[0];
10413 var sourceRest = sourceHistory.slice(1);
10414 var T = targetHistory[0];
10415 var targetRest = targetHistory.slice(1);
10416
10417 if (!S || targetHistory.length === 0) {
10418 return {
10419 last_seq: LOWEST_SEQ,
10420 history: []
10421 };
10422 }
10423
10424 var sourceId = S.session_id;
10425 /* istanbul ignore if */
10426 if (hasSessionId(sourceId, targetHistory)) {
10427 return {
10428 last_seq: S.last_seq,
10429 history: sourceHistory
10430 };
10431 }
10432
10433 var targetId = T.session_id;
10434 if (hasSessionId(targetId, sourceRest)) {
10435 return {
10436 last_seq: T.last_seq,
10437 history: targetRest
10438 };
10439 }
10440
10441 return compareReplicationHistory(sourceRest, targetRest);
10442}
10443
10444function hasSessionId(sessionId, history) {
10445 var props = history[0];
10446 var rest = history.slice(1);
10447
10448 if (!sessionId || history.length === 0) {
10449 return false;
10450 }
10451
10452 if (sessionId === props.session_id) {
10453 return true;
10454 }
10455
10456 return hasSessionId(sessionId, rest);
10457}
10458
10459function isForbiddenError(err) {
10460 return typeof err.status === 'number' && Math.floor(err.status / 100) === 4;
10461}
10462
10463var STARTING_BACK_OFF = 0;
10464
10465function backOff(opts, returnValue, error, callback) {
10466 if (opts.retry === false) {
10467 returnValue.emit('error', error);
10468 returnValue.removeAllListeners();
10469 return;
10470 }
10471 /* istanbul ignore if */
10472 if (typeof opts.back_off_function !== 'function') {
10473 opts.back_off_function = defaultBackOff;
10474 }
10475 returnValue.emit('requestError', error);
10476 if (returnValue.state === 'active' || returnValue.state === 'pending') {
10477 returnValue.emit('paused', error);
10478 returnValue.state = 'stopped';
10479 var backOffSet = function backoffTimeSet() {
10480 opts.current_back_off = STARTING_BACK_OFF;
10481 };
10482 var removeBackOffSetter = function removeBackOffTimeSet() {
10483 returnValue.removeListener('active', backOffSet);
10484 };
10485 returnValue.once('paused', removeBackOffSetter);
10486 returnValue.once('active', backOffSet);
10487 }
10488
10489 opts.current_back_off = opts.current_back_off || STARTING_BACK_OFF;
10490 opts.current_back_off = opts.back_off_function(opts.current_back_off);
10491 setTimeout(callback, opts.current_back_off);
10492}
10493
10494function sortObjectPropertiesByKey(queryParams) {
10495 return Object.keys(queryParams).sort(collate).reduce(function (result, key) {
10496 result[key] = queryParams[key];
10497 return result;
10498 }, {});
10499}
10500
10501// Generate a unique id particular to this replication.
10502// Not guaranteed to align perfectly with CouchDB's rep ids.
10503function generateReplicationId(src, target, opts) {
10504 var docIds = opts.doc_ids ? opts.doc_ids.sort(collate) : '';
10505 var filterFun = opts.filter ? opts.filter.toString() : '';
10506 var queryParams = '';
10507 var filterViewName = '';
10508 var selector = '';
10509
10510 // possibility for checkpoints to be lost here as behaviour of
10511 // JSON.stringify is not stable (see #6226)
10512 /* istanbul ignore if */
10513 if (opts.selector) {
10514 selector = JSON.stringify(opts.selector);
10515 }
10516
10517 if (opts.filter && opts.query_params) {
10518 queryParams = JSON.stringify(sortObjectPropertiesByKey(opts.query_params));
10519 }
10520
10521 if (opts.filter && opts.filter === '_view') {
10522 filterViewName = opts.view.toString();
10523 }
10524
10525 return Promise.all([src.id(), target.id()]).then(function (res) {
10526 var queryData = res[0] + res[1] + filterFun + filterViewName +
10527 queryParams + docIds + selector;
10528 return new Promise(function (resolve) {
10529 binaryMd5(queryData, resolve);
10530 });
10531 }).then(function (md5sum) {
10532 // can't use straight-up md5 alphabet, because
10533 // the char '/' is interpreted as being for attachments,
10534 // and + is also not url-safe
10535 md5sum = md5sum.replace(/\//g, '.').replace(/\+/g, '_');
10536 return '_local/' + md5sum;
10537 });
10538}
10539
10540function replicate(src, target, opts, returnValue, result) {
10541 var batches = []; // list of batches to be processed
10542 var currentBatch; // the batch currently being processed
10543 var pendingBatch = {
10544 seq: 0,
10545 changes: [],
10546 docs: []
10547 }; // next batch, not yet ready to be processed
10548 var writingCheckpoint = false; // true while checkpoint is being written
10549 var changesCompleted = false; // true when all changes received
10550 var replicationCompleted = false; // true when replication has completed
10551 var last_seq = 0;
10552 var continuous = opts.continuous || opts.live || false;
10553 var batch_size = opts.batch_size || 100;
10554 var batches_limit = opts.batches_limit || 10;
10555 var changesPending = false; // true while src.changes is running
10556 var doc_ids = opts.doc_ids;
10557 var selector = opts.selector;
10558 var repId;
10559 var checkpointer;
10560 var changedDocs = [];
10561 // Like couchdb, every replication gets a unique session id
10562 var session = uuid();
10563
10564 result = result || {
10565 ok: true,
10566 start_time: new Date().toISOString(),
10567 docs_read: 0,
10568 docs_written: 0,
10569 doc_write_failures: 0,
10570 errors: []
10571 };
10572
10573 var changesOpts = {};
10574 returnValue.ready(src, target);
10575
10576 function initCheckpointer() {
10577 if (checkpointer) {
10578 return Promise.resolve();
10579 }
10580 return generateReplicationId(src, target, opts).then(function (res) {
10581 repId = res;
10582
10583 var checkpointOpts = {};
10584 if (opts.checkpoint === false) {
10585 checkpointOpts = { writeSourceCheckpoint: false, writeTargetCheckpoint: false };
10586 } else if (opts.checkpoint === 'source') {
10587 checkpointOpts = { writeSourceCheckpoint: true, writeTargetCheckpoint: false };
10588 } else if (opts.checkpoint === 'target') {
10589 checkpointOpts = { writeSourceCheckpoint: false, writeTargetCheckpoint: true };
10590 } else {
10591 checkpointOpts = { writeSourceCheckpoint: true, writeTargetCheckpoint: true };
10592 }
10593
10594 checkpointer = new Checkpointer(src, target, repId, returnValue, checkpointOpts);
10595 });
10596 }
10597
10598 function writeDocs() {
10599 changedDocs = [];
10600
10601 if (currentBatch.docs.length === 0) {
10602 return;
10603 }
10604 var docs = currentBatch.docs;
10605 var bulkOpts = {timeout: opts.timeout};
10606 return target.bulkDocs({docs: docs, new_edits: false}, bulkOpts).then(function (res) {
10607 /* istanbul ignore if */
10608 if (returnValue.cancelled) {
10609 completeReplication();
10610 throw new Error('cancelled');
10611 }
10612
10613 // `res` doesn't include full documents (which live in `docs`), so we create a map of
10614 // (id -> error), and check for errors while iterating over `docs`
10615 var errorsById = Object.create(null);
10616 res.forEach(function (res) {
10617 if (res.error) {
10618 errorsById[res.id] = res;
10619 }
10620 });
10621
10622 var errorsNo = Object.keys(errorsById).length;
10623 result.doc_write_failures += errorsNo;
10624 result.docs_written += docs.length - errorsNo;
10625
10626 docs.forEach(function (doc) {
10627 var error = errorsById[doc._id];
10628 if (error) {
10629 result.errors.push(error);
10630 // Normalize error name. i.e. 'Unauthorized' -> 'unauthorized' (eg Sync Gateway)
10631 var errorName = (error.name || '').toLowerCase();
10632 if (errorName === 'unauthorized' || errorName === 'forbidden') {
10633 returnValue.emit('denied', clone(error));
10634 } else {
10635 throw error;
10636 }
10637 } else {
10638 changedDocs.push(doc);
10639 }
10640 });
10641
10642 }, function (err) {
10643 result.doc_write_failures += docs.length;
10644 throw err;
10645 });
10646 }
10647
10648 function finishBatch() {
10649 if (currentBatch.error) {
10650 throw new Error('There was a problem getting docs.');
10651 }
10652 result.last_seq = last_seq = currentBatch.seq;
10653 var outResult = clone(result);
10654 if (changedDocs.length) {
10655 outResult.docs = changedDocs;
10656 // Attach 'pending' property if server supports it (CouchDB 2.0+)
10657 /* istanbul ignore if */
10658 if (typeof currentBatch.pending === 'number') {
10659 outResult.pending = currentBatch.pending;
10660 delete currentBatch.pending;
10661 }
10662 returnValue.emit('change', outResult);
10663 }
10664 writingCheckpoint = true;
10665 return checkpointer.writeCheckpoint(currentBatch.seq,
10666 session).then(function () {
10667 writingCheckpoint = false;
10668 /* istanbul ignore if */
10669 if (returnValue.cancelled) {
10670 completeReplication();
10671 throw new Error('cancelled');
10672 }
10673 currentBatch = undefined;
10674 getChanges();
10675 })["catch"](function (err) {
10676 onCheckpointError(err);
10677 throw err;
10678 });
10679 }
10680
10681 function getDiffs() {
10682 var diff = {};
10683 currentBatch.changes.forEach(function (change) {
10684 // Couchbase Sync Gateway emits these, but we can ignore them
10685 /* istanbul ignore if */
10686 if (change.id === "_user/") {
10687 return;
10688 }
10689 diff[change.id] = change.changes.map(function (x) {
10690 return x.rev;
10691 });
10692 });
10693 return target.revsDiff(diff).then(function (diffs) {
10694 /* istanbul ignore if */
10695 if (returnValue.cancelled) {
10696 completeReplication();
10697 throw new Error('cancelled');
10698 }
10699 // currentBatch.diffs elements are deleted as the documents are written
10700 currentBatch.diffs = diffs;
10701 });
10702 }
10703
10704 function getBatchDocs() {
10705 return getDocs(src, target, currentBatch.diffs, returnValue).then(function (got) {
10706 currentBatch.error = !got.ok;
10707 got.docs.forEach(function (doc) {
10708 delete currentBatch.diffs[doc._id];
10709 result.docs_read++;
10710 currentBatch.docs.push(doc);
10711 });
10712 });
10713 }
10714
10715 function startNextBatch() {
10716 if (returnValue.cancelled || currentBatch) {
10717 return;
10718 }
10719 if (batches.length === 0) {
10720 processPendingBatch(true);
10721 return;
10722 }
10723 currentBatch = batches.shift();
10724 getDiffs()
10725 .then(getBatchDocs)
10726 .then(writeDocs)
10727 .then(finishBatch)
10728 .then(startNextBatch)[
10729 "catch"](function (err) {
10730 abortReplication('batch processing terminated with error', err);
10731 });
10732 }
10733
10734
10735 function processPendingBatch(immediate$$1) {
10736 if (pendingBatch.changes.length === 0) {
10737 if (batches.length === 0 && !currentBatch) {
10738 if ((continuous && changesOpts.live) || changesCompleted) {
10739 returnValue.state = 'pending';
10740 returnValue.emit('paused');
10741 }
10742 if (changesCompleted) {
10743 completeReplication();
10744 }
10745 }
10746 return;
10747 }
10748 if (
10749 immediate$$1 ||
10750 changesCompleted ||
10751 pendingBatch.changes.length >= batch_size
10752 ) {
10753 batches.push(pendingBatch);
10754 pendingBatch = {
10755 seq: 0,
10756 changes: [],
10757 docs: []
10758 };
10759 if (returnValue.state === 'pending' || returnValue.state === 'stopped') {
10760 returnValue.state = 'active';
10761 returnValue.emit('active');
10762 }
10763 startNextBatch();
10764 }
10765 }
10766
10767
10768 function abortReplication(reason, err) {
10769 if (replicationCompleted) {
10770 return;
10771 }
10772 if (!err.message) {
10773 err.message = reason;
10774 }
10775 result.ok = false;
10776 result.status = 'aborting';
10777 batches = [];
10778 pendingBatch = {
10779 seq: 0,
10780 changes: [],
10781 docs: []
10782 };
10783 completeReplication(err);
10784 }
10785
10786
10787 function completeReplication(fatalError) {
10788 if (replicationCompleted) {
10789 return;
10790 }
10791 /* istanbul ignore if */
10792 if (returnValue.cancelled) {
10793 result.status = 'cancelled';
10794 if (writingCheckpoint) {
10795 return;
10796 }
10797 }
10798 result.status = result.status || 'complete';
10799 result.end_time = new Date().toISOString();
10800 result.last_seq = last_seq;
10801 replicationCompleted = true;
10802
10803 if (fatalError) {
10804 // need to extend the error because Firefox considers ".result" read-only
10805 fatalError = createError(fatalError);
10806 fatalError.result = result;
10807
10808 // Normalize error name. i.e. 'Unauthorized' -> 'unauthorized' (eg Sync Gateway)
10809 var errorName = (fatalError.name || '').toLowerCase();
10810 if (errorName === 'unauthorized' || errorName === 'forbidden') {
10811 returnValue.emit('error', fatalError);
10812 returnValue.removeAllListeners();
10813 } else {
10814 backOff(opts, returnValue, fatalError, function () {
10815 replicate(src, target, opts, returnValue);
10816 });
10817 }
10818 } else {
10819 returnValue.emit('complete', result);
10820 returnValue.removeAllListeners();
10821 }
10822 }
10823
10824
10825 function onChange(change, pending, lastSeq) {
10826 /* istanbul ignore if */
10827 if (returnValue.cancelled) {
10828 return completeReplication();
10829 }
10830 // Attach 'pending' property if server supports it (CouchDB 2.0+)
10831 /* istanbul ignore if */
10832 if (typeof pending === 'number') {
10833 pendingBatch.pending = pending;
10834 }
10835
10836 var filter = filterChange(opts)(change);
10837 if (!filter) {
10838 return;
10839 }
10840 pendingBatch.seq = change.seq || lastSeq;
10841 pendingBatch.changes.push(change);
10842 immediate(function () {
10843 processPendingBatch(batches.length === 0 && changesOpts.live);
10844 });
10845 }
10846
10847
10848 function onChangesComplete(changes) {
10849 changesPending = false;
10850 /* istanbul ignore if */
10851 if (returnValue.cancelled) {
10852 return completeReplication();
10853 }
10854
10855 // if no results were returned then we're done,
10856 // else fetch more
10857 if (changes.results.length > 0) {
10858 changesOpts.since = changes.results[changes.results.length - 1].seq;
10859 getChanges();
10860 processPendingBatch(true);
10861 } else {
10862
10863 var complete = function () {
10864 if (continuous) {
10865 changesOpts.live = true;
10866 getChanges();
10867 } else {
10868 changesCompleted = true;
10869 }
10870 processPendingBatch(true);
10871 };
10872
10873 // update the checkpoint so we start from the right seq next time
10874 if (!currentBatch && changes.results.length === 0) {
10875 writingCheckpoint = true;
10876 checkpointer.writeCheckpoint(changes.last_seq,
10877 session).then(function () {
10878 writingCheckpoint = false;
10879 result.last_seq = last_seq = changes.last_seq;
10880 complete();
10881 })[
10882 "catch"](onCheckpointError);
10883 } else {
10884 complete();
10885 }
10886 }
10887 }
10888
10889
10890 function onChangesError(err) {
10891 changesPending = false;
10892 /* istanbul ignore if */
10893 if (returnValue.cancelled) {
10894 return completeReplication();
10895 }
10896 abortReplication('changes rejected', err);
10897 }
10898
10899
10900 function getChanges() {
10901 if (!(
10902 !changesPending &&
10903 !changesCompleted &&
10904 batches.length < batches_limit
10905 )) {
10906 return;
10907 }
10908 changesPending = true;
10909 function abortChanges() {
10910 changes.cancel();
10911 }
10912 function removeListener() {
10913 returnValue.removeListener('cancel', abortChanges);
10914 }
10915
10916 if (returnValue._changes) { // remove old changes() and listeners
10917 returnValue.removeListener('cancel', returnValue._abortChanges);
10918 returnValue._changes.cancel();
10919 }
10920 returnValue.once('cancel', abortChanges);
10921
10922 var changes = src.changes(changesOpts)
10923 .on('change', onChange);
10924 changes.then(removeListener, removeListener);
10925 changes.then(onChangesComplete)[
10926 "catch"](onChangesError);
10927
10928 if (opts.retry) {
10929 // save for later so we can cancel if necessary
10930 returnValue._changes = changes;
10931 returnValue._abortChanges = abortChanges;
10932 }
10933 }
10934
10935
10936 function startChanges() {
10937 initCheckpointer().then(function () {
10938 /* istanbul ignore if */
10939 if (returnValue.cancelled) {
10940 completeReplication();
10941 return;
10942 }
10943 return checkpointer.getCheckpoint().then(function (checkpoint) {
10944 last_seq = checkpoint;
10945 changesOpts = {
10946 since: last_seq,
10947 limit: batch_size,
10948 batch_size: batch_size,
10949 style: 'all_docs',
10950 doc_ids: doc_ids,
10951 selector: selector,
10952 return_docs: true // required so we know when we're done
10953 };
10954 if (opts.filter) {
10955 if (typeof opts.filter !== 'string') {
10956 // required for the client-side filter in onChange
10957 changesOpts.include_docs = true;
10958 } else { // ddoc filter
10959 changesOpts.filter = opts.filter;
10960 }
10961 }
10962 if ('heartbeat' in opts) {
10963 changesOpts.heartbeat = opts.heartbeat;
10964 }
10965 if ('timeout' in opts) {
10966 changesOpts.timeout = opts.timeout;
10967 }
10968 if (opts.query_params) {
10969 changesOpts.query_params = opts.query_params;
10970 }
10971 if (opts.view) {
10972 changesOpts.view = opts.view;
10973 }
10974 getChanges();
10975 });
10976 })["catch"](function (err) {
10977 abortReplication('getCheckpoint rejected with ', err);
10978 });
10979 }
10980
10981 /* istanbul ignore next */
10982 function onCheckpointError(err) {
10983 writingCheckpoint = false;
10984 abortReplication('writeCheckpoint completed with error', err);
10985 }
10986
10987 /* istanbul ignore if */
10988 if (returnValue.cancelled) { // cancelled immediately
10989 completeReplication();
10990 return;
10991 }
10992
10993 if (!returnValue._addedListeners) {
10994 returnValue.once('cancel', completeReplication);
10995
10996 if (typeof opts.complete === 'function') {
10997 returnValue.once('error', opts.complete);
10998 returnValue.once('complete', function (result) {
10999 opts.complete(null, result);
11000 });
11001 }
11002 returnValue._addedListeners = true;
11003 }
11004
11005 if (typeof opts.since === 'undefined') {
11006 startChanges();
11007 } else {
11008 initCheckpointer().then(function () {
11009 writingCheckpoint = true;
11010 return checkpointer.writeCheckpoint(opts.since, session);
11011 }).then(function () {
11012 writingCheckpoint = false;
11013 /* istanbul ignore if */
11014 if (returnValue.cancelled) {
11015 completeReplication();
11016 return;
11017 }
11018 last_seq = opts.since;
11019 startChanges();
11020 })["catch"](onCheckpointError);
11021 }
11022}
11023
11024// We create a basic promise so the caller can cancel the replication possibly
11025// before we have actually started listening to changes etc
11026inherits(Replication, events.EventEmitter);
11027function Replication() {
11028 events.EventEmitter.call(this);
11029 this.cancelled = false;
11030 this.state = 'pending';
11031 var self = this;
11032 var promise = new Promise(function (fulfill, reject) {
11033 self.once('complete', fulfill);
11034 self.once('error', reject);
11035 });
11036 self.then = function (resolve, reject) {
11037 return promise.then(resolve, reject);
11038 };
11039 self["catch"] = function (reject) {
11040 return promise["catch"](reject);
11041 };
11042 // As we allow error handling via "error" event as well,
11043 // put a stub in here so that rejecting never throws UnhandledError.
11044 self["catch"](function () {});
11045}
11046
11047Replication.prototype.cancel = function () {
11048 this.cancelled = true;
11049 this.state = 'cancelled';
11050 this.emit('cancel');
11051};
11052
11053Replication.prototype.ready = function (src, target) {
11054 var self = this;
11055 if (self._readyCalled) {
11056 return;
11057 }
11058 self._readyCalled = true;
11059
11060 function onDestroy() {
11061 self.cancel();
11062 }
11063 src.once('destroyed', onDestroy);
11064 target.once('destroyed', onDestroy);
11065 function cleanup() {
11066 src.removeListener('destroyed', onDestroy);
11067 target.removeListener('destroyed', onDestroy);
11068 }
11069 self.once('complete', cleanup);
11070};
11071
11072function toPouch(db, opts) {
11073 var PouchConstructor = opts.PouchConstructor;
11074 if (typeof db === 'string') {
11075 return new PouchConstructor(db, opts);
11076 } else {
11077 return db;
11078 }
11079}
11080
11081function replicateWrapper(src, target, opts, callback) {
11082
11083 if (typeof opts === 'function') {
11084 callback = opts;
11085 opts = {};
11086 }
11087 if (typeof opts === 'undefined') {
11088 opts = {};
11089 }
11090
11091 if (opts.doc_ids && !Array.isArray(opts.doc_ids)) {
11092 throw createError(BAD_REQUEST,
11093 "`doc_ids` filter parameter is not a list.");
11094 }
11095
11096 opts.complete = callback;
11097 opts = clone(opts);
11098 opts.continuous = opts.continuous || opts.live;
11099 opts.retry = ('retry' in opts) ? opts.retry : false;
11100 /*jshint validthis:true */
11101 opts.PouchConstructor = opts.PouchConstructor || this;
11102 var replicateRet = new Replication(opts);
11103 var srcPouch = toPouch(src, opts);
11104 var targetPouch = toPouch(target, opts);
11105 replicate(srcPouch, targetPouch, opts, replicateRet);
11106 return replicateRet;
11107}
11108
11109inherits(Sync, events.EventEmitter);
11110function sync(src, target, opts, callback) {
11111 if (typeof opts === 'function') {
11112 callback = opts;
11113 opts = {};
11114 }
11115 if (typeof opts === 'undefined') {
11116 opts = {};
11117 }
11118 opts = clone(opts);
11119 /*jshint validthis:true */
11120 opts.PouchConstructor = opts.PouchConstructor || this;
11121 src = toPouch(src, opts);
11122 target = toPouch(target, opts);
11123 return new Sync(src, target, opts, callback);
11124}
11125
11126function Sync(src, target, opts, callback) {
11127 var self = this;
11128 this.canceled = false;
11129
11130 var optsPush = opts.push ? $inject_Object_assign({}, opts, opts.push) : opts;
11131 var optsPull = opts.pull ? $inject_Object_assign({}, opts, opts.pull) : opts;
11132
11133 this.push = replicateWrapper(src, target, optsPush);
11134 this.pull = replicateWrapper(target, src, optsPull);
11135
11136 this.pushPaused = true;
11137 this.pullPaused = true;
11138
11139 function pullChange(change) {
11140 self.emit('change', {
11141 direction: 'pull',
11142 change: change
11143 });
11144 }
11145 function pushChange(change) {
11146 self.emit('change', {
11147 direction: 'push',
11148 change: change
11149 });
11150 }
11151 function pushDenied(doc) {
11152 self.emit('denied', {
11153 direction: 'push',
11154 doc: doc
11155 });
11156 }
11157 function pullDenied(doc) {
11158 self.emit('denied', {
11159 direction: 'pull',
11160 doc: doc
11161 });
11162 }
11163 function pushPaused() {
11164 self.pushPaused = true;
11165 /* istanbul ignore if */
11166 if (self.pullPaused) {
11167 self.emit('paused');
11168 }
11169 }
11170 function pullPaused() {
11171 self.pullPaused = true;
11172 /* istanbul ignore if */
11173 if (self.pushPaused) {
11174 self.emit('paused');
11175 }
11176 }
11177 function pushActive() {
11178 self.pushPaused = false;
11179 /* istanbul ignore if */
11180 if (self.pullPaused) {
11181 self.emit('active', {
11182 direction: 'push'
11183 });
11184 }
11185 }
11186 function pullActive() {
11187 self.pullPaused = false;
11188 /* istanbul ignore if */
11189 if (self.pushPaused) {
11190 self.emit('active', {
11191 direction: 'pull'
11192 });
11193 }
11194 }
11195
11196 var removed = {};
11197
11198 function removeAll(type) { // type is 'push' or 'pull'
11199 return function (event, func) {
11200 var isChange = event === 'change' &&
11201 (func === pullChange || func === pushChange);
11202 var isDenied = event === 'denied' &&
11203 (func === pullDenied || func === pushDenied);
11204 var isPaused = event === 'paused' &&
11205 (func === pullPaused || func === pushPaused);
11206 var isActive = event === 'active' &&
11207 (func === pullActive || func === pushActive);
11208
11209 if (isChange || isDenied || isPaused || isActive) {
11210 if (!(event in removed)) {
11211 removed[event] = {};
11212 }
11213 removed[event][type] = true;
11214 if (Object.keys(removed[event]).length === 2) {
11215 // both push and pull have asked to be removed
11216 self.removeAllListeners(event);
11217 }
11218 }
11219 };
11220 }
11221
11222 if (opts.live) {
11223 this.push.on('complete', self.pull.cancel.bind(self.pull));
11224 this.pull.on('complete', self.push.cancel.bind(self.push));
11225 }
11226
11227 function addOneListener(ee, event, listener) {
11228 if (ee.listeners(event).indexOf(listener) == -1) {
11229 ee.on(event, listener);
11230 }
11231 }
11232
11233 this.on('newListener', function (event) {
11234 if (event === 'change') {
11235 addOneListener(self.pull, 'change', pullChange);
11236 addOneListener(self.push, 'change', pushChange);
11237 } else if (event === 'denied') {
11238 addOneListener(self.pull, 'denied', pullDenied);
11239 addOneListener(self.push, 'denied', pushDenied);
11240 } else if (event === 'active') {
11241 addOneListener(self.pull, 'active', pullActive);
11242 addOneListener(self.push, 'active', pushActive);
11243 } else if (event === 'paused') {
11244 addOneListener(self.pull, 'paused', pullPaused);
11245 addOneListener(self.push, 'paused', pushPaused);
11246 }
11247 });
11248
11249 this.on('removeListener', function (event) {
11250 if (event === 'change') {
11251 self.pull.removeListener('change', pullChange);
11252 self.push.removeListener('change', pushChange);
11253 } else if (event === 'denied') {
11254 self.pull.removeListener('denied', pullDenied);
11255 self.push.removeListener('denied', pushDenied);
11256 } else if (event === 'active') {
11257 self.pull.removeListener('active', pullActive);
11258 self.push.removeListener('active', pushActive);
11259 } else if (event === 'paused') {
11260 self.pull.removeListener('paused', pullPaused);
11261 self.push.removeListener('paused', pushPaused);
11262 }
11263 });
11264
11265 this.pull.on('removeListener', removeAll('pull'));
11266 this.push.on('removeListener', removeAll('push'));
11267
11268 var promise = Promise.all([
11269 this.push,
11270 this.pull
11271 ]).then(function (resp) {
11272 var out = {
11273 push: resp[0],
11274 pull: resp[1]
11275 };
11276 self.emit('complete', out);
11277 if (callback) {
11278 callback(null, out);
11279 }
11280 self.removeAllListeners();
11281 return out;
11282 }, function (err) {
11283 self.cancel();
11284 if (callback) {
11285 // if there's a callback, then the callback can receive
11286 // the error event
11287 callback(err);
11288 } else {
11289 // if there's no callback, then we're safe to emit an error
11290 // event, which would otherwise throw an unhandled error
11291 // due to 'error' being a special event in EventEmitters
11292 self.emit('error', err);
11293 }
11294 self.removeAllListeners();
11295 if (callback) {
11296 // no sense throwing if we're already emitting an 'error' event
11297 throw err;
11298 }
11299 });
11300
11301 this.then = function (success, err) {
11302 return promise.then(success, err);
11303 };
11304
11305 this["catch"] = function (err) {
11306 return promise["catch"](err);
11307 };
11308}
11309
11310Sync.prototype.cancel = function () {
11311 if (!this.canceled) {
11312 this.canceled = true;
11313 this.push.cancel();
11314 this.pull.cancel();
11315 }
11316};
11317
11318function replication(PouchDB) {
11319 PouchDB.replicate = replicateWrapper;
11320 PouchDB.sync = sync;
11321
11322 Object.defineProperty(PouchDB.prototype, 'replicate', {
11323 get: function () {
11324 var self = this;
11325 if (typeof this.replicateMethods === 'undefined') {
11326 this.replicateMethods = {
11327 from: function (other, opts, callback) {
11328 return self.constructor.replicate(other, self, opts, callback);
11329 },
11330 to: function (other, opts, callback) {
11331 return self.constructor.replicate(self, other, opts, callback);
11332 }
11333 };
11334 }
11335 return this.replicateMethods;
11336 }
11337 });
11338
11339 PouchDB.prototype.sync = function (dbName, opts, callback) {
11340 return this.constructor.sync(this, dbName, opts, callback);
11341 };
11342}
11343
11344var next$1 = PouchDB
11345 .plugin(next)
11346 .plugin(httpPouch)
11347 .plugin(replication)
11348 .plugin(mapreduce);
11349
11350module.exports = next$1;
11351
11352}).call(this,_dereq_(5),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
11353},{"1":1,"2":2,"3":3,"4":4,"5":5,"6":6,"7":7}]},{},[12])(12)
11354});
11355
\No newline at end of file