pouchdb-mapreduce
Advanced tools
Comparing version
@@ -5,12 +5,853 @@ 'use strict'; | ||
var pouchdbUtils = require('pouchdb-utils'); | ||
var pouchdbBinaryUtils = require('pouchdb-binary-utils'); | ||
var pouchdbCollate = require('pouchdb-collate'); | ||
var Promise = _interopDefault(require('pouchdb-promise')); | ||
var pouchdbMd5 = require('pouchdb-md5'); | ||
var lie = _interopDefault(require('lie')); | ||
var getArguments = _interopDefault(require('argsarray')); | ||
var debug = _interopDefault(require('debug')); | ||
var events = require('events'); | ||
var inherits = _interopDefault(require('inherits')); | ||
var Md5 = _interopDefault(require('spark-md5')); | ||
var scopedEval = _interopDefault(require('scope-eval')); | ||
var pouchdbMapreduceUtils = require('pouchdb-mapreduce-utils'); | ||
var inherits = _interopDefault(require('inherits')); | ||
/* istanbul ignore next */ | ||
var PouchPromise = typeof Promise === 'function' ? Promise : lie; | ||
// most of this is borrowed from lodash.isPlainObject: | ||
// https://github.com/fis-components/lodash.isplainobject/ | ||
// blob/29c358140a74f252aeb08c9eb28bef86f2217d4a/index.js | ||
var funcToString = Function.prototype.toString; | ||
var objectCtorString = funcToString.call(Object); | ||
var log$1 = debug('pouchdb:api'); | ||
// like underscore/lodash _.pick() | ||
function pick(obj, arr) { | ||
var res = {}; | ||
for (var i = 0, len = arr.length; i < len; i++) { | ||
var prop = arr[i]; | ||
if (prop in obj) { | ||
res[prop] = obj[prop]; | ||
} | ||
} | ||
return res; | ||
} | ||
function isChromeApp() { | ||
return (typeof chrome !== "undefined" && | ||
typeof chrome.storage !== "undefined" && | ||
typeof chrome.storage.local !== "undefined"); | ||
} | ||
var hasLocal; | ||
if (isChromeApp()) { | ||
hasLocal = false; | ||
} else { | ||
try { | ||
localStorage.setItem('_pouch_check_localstorage', 1); | ||
hasLocal = !!localStorage.getItem('_pouch_check_localstorage'); | ||
} catch (e) { | ||
hasLocal = false; | ||
} | ||
} | ||
function hasLocalStorage() { | ||
return hasLocal; | ||
} | ||
inherits(Changes, events.EventEmitter); | ||
/* istanbul ignore next */ | ||
function attachBrowserEvents(self) { | ||
if (isChromeApp()) { | ||
chrome.storage.onChanged.addListener(function (e) { | ||
// make sure it's event addressed to us | ||
if (e.db_name != null) { | ||
//object only has oldValue, newValue members | ||
self.emit(e.dbName.newValue); | ||
} | ||
}); | ||
} else if (hasLocalStorage()) { | ||
if (typeof addEventListener !== 'undefined') { | ||
addEventListener("storage", function (e) { | ||
self.emit(e.key); | ||
}); | ||
} else { // old IE | ||
window.attachEvent("storage", function (e) { | ||
self.emit(e.key); | ||
}); | ||
} | ||
} | ||
} | ||
function Changes() { | ||
events.EventEmitter.call(this); | ||
this._listeners = {}; | ||
attachBrowserEvents(this); | ||
} | ||
Changes.prototype.addListener = function (dbName, id, db, opts) { | ||
/* istanbul ignore if */ | ||
if (this._listeners[id]) { | ||
return; | ||
} | ||
var self = this; | ||
var inprogress = false; | ||
function eventFunction() { | ||
/* istanbul ignore if */ | ||
if (!self._listeners[id]) { | ||
return; | ||
} | ||
if (inprogress) { | ||
inprogress = 'waiting'; | ||
return; | ||
} | ||
inprogress = true; | ||
var changesOpts = pick(opts, [ | ||
'style', 'include_docs', 'attachments', 'conflicts', 'filter', | ||
'doc_ids', 'view', 'since', 'query_params', 'binary' | ||
]); | ||
/* istanbul ignore next */ | ||
function onError() { | ||
inprogress = false; | ||
} | ||
db.changes(changesOpts).on('change', function (c) { | ||
if (c.seq > opts.since && !opts.cancelled) { | ||
opts.since = c.seq; | ||
opts.onChange(c); | ||
} | ||
}).on('complete', function () { | ||
if (inprogress === 'waiting') { | ||
setTimeout(function (){ | ||
eventFunction(); | ||
},0); | ||
} | ||
inprogress = false; | ||
}).on('error', onError); | ||
} | ||
this._listeners[id] = eventFunction; | ||
this.on(dbName, eventFunction); | ||
}; | ||
Changes.prototype.removeListener = function (dbName, id) { | ||
/* istanbul ignore if */ | ||
if (!(id in this._listeners)) { | ||
return; | ||
} | ||
events.EventEmitter.prototype.removeListener.call(this, dbName, | ||
this._listeners[id]); | ||
delete this._listeners[id]; | ||
}; | ||
/* istanbul ignore next */ | ||
Changes.prototype.notifyLocalWindows = function (dbName) { | ||
//do a useless change on a storage thing | ||
//in order to get other windows's listeners to activate | ||
if (isChromeApp()) { | ||
chrome.storage.local.set({dbName: dbName}); | ||
} else if (hasLocalStorage()) { | ||
localStorage[dbName] = (localStorage[dbName] === "a") ? "b" : "a"; | ||
} | ||
}; | ||
Changes.prototype.notify = function (dbName) { | ||
this.emit(dbName); | ||
this.notifyLocalWindows(dbName); | ||
}; | ||
function guardedConsole(method) { | ||
/* istanbul ignore else */ | ||
if (console !== 'undefined' && method in console) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
console[method].apply(console, args); | ||
} | ||
} | ||
inherits(PouchError, Error); | ||
function PouchError(opts) { | ||
Error.call(this, opts.reason); | ||
this.status = opts.status; | ||
this.name = opts.error; | ||
this.message = opts.reason; | ||
this.error = true; | ||
} | ||
PouchError.prototype.toString = function () { | ||
return JSON.stringify({ | ||
status: this.status, | ||
name: this.name, | ||
message: this.message, | ||
reason: this.reason | ||
}); | ||
}; | ||
var UNAUTHORIZED = new PouchError({ | ||
status: 401, | ||
error: 'unauthorized', | ||
reason: "Name or password is incorrect." | ||
}); | ||
var MISSING_BULK_DOCS = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: "Missing JSON list of 'docs'" | ||
}); | ||
var MISSING_DOC = new PouchError({ | ||
status: 404, | ||
error: 'not_found', | ||
reason: 'missing' | ||
}); | ||
var REV_CONFLICT = new PouchError({ | ||
status: 409, | ||
error: 'conflict', | ||
reason: 'Document update conflict' | ||
}); | ||
var INVALID_ID = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: '_id field must contain a string' | ||
}); | ||
var MISSING_ID = new PouchError({ | ||
status: 412, | ||
error: 'missing_id', | ||
reason: '_id is required for puts' | ||
}); | ||
var RESERVED_ID = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: 'Only reserved document ids may start with underscore.' | ||
}); | ||
var NOT_OPEN = new PouchError({ | ||
status: 412, | ||
error: 'precondition_failed', | ||
reason: 'Database not open' | ||
}); | ||
var UNKNOWN_ERROR = new PouchError({ | ||
status: 500, | ||
error: 'unknown_error', | ||
reason: 'Database encountered an unknown error' | ||
}); | ||
var BAD_ARG = new PouchError({ | ||
status: 500, | ||
error: 'badarg', | ||
reason: 'Some query argument is invalid' | ||
}); | ||
var INVALID_REQUEST = new PouchError({ | ||
status: 400, | ||
error: 'invalid_request', | ||
reason: 'Request was invalid' | ||
}); | ||
var QUERY_PARSE_ERROR = new PouchError({ | ||
status: 400, | ||
error: 'query_parse_error', | ||
reason: 'Some query parameter is invalid' | ||
}); | ||
var DOC_VALIDATION = new PouchError({ | ||
status: 500, | ||
error: 'doc_validation', | ||
reason: 'Bad special document member' | ||
}); | ||
var BAD_REQUEST = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: 'Something wrong with the request' | ||
}); | ||
var NOT_AN_OBJECT = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: 'Document must be a JSON object' | ||
}); | ||
var DB_MISSING = new PouchError({ | ||
status: 404, | ||
error: 'not_found', | ||
reason: 'Database not found' | ||
}); | ||
var IDB_ERROR = new PouchError({ | ||
status: 500, | ||
error: 'indexed_db_went_bad', | ||
reason: 'unknown' | ||
}); | ||
var WSQ_ERROR = new PouchError({ | ||
status: 500, | ||
error: 'web_sql_went_bad', | ||
reason: 'unknown' | ||
}); | ||
var LDB_ERROR = new PouchError({ | ||
status: 500, | ||
error: 'levelDB_went_went_bad', | ||
reason: 'unknown' | ||
}); | ||
var FORBIDDEN = new PouchError({ | ||
status: 403, | ||
error: 'forbidden', | ||
reason: 'Forbidden by design doc validate_doc_update function' | ||
}); | ||
var INVALID_REV = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: 'Invalid rev format' | ||
}); | ||
var FILE_EXISTS = new PouchError({ | ||
status: 412, | ||
error: 'file_exists', | ||
reason: 'The database could not be created, the file already exists.' | ||
}); | ||
var MISSING_STUB = new PouchError({ | ||
status: 412, | ||
error: 'missing_stub' | ||
}); | ||
var INVALID_URL = new PouchError({ | ||
status: 413, | ||
error: 'invalid_url', | ||
reason: 'Provided URL is invalid' | ||
}); | ||
function flatten(arrs) { | ||
var res = []; | ||
for (var i = 0, len = arrs.length; i < len; i++) { | ||
res = res.concat(arrs[i]); | ||
} | ||
return res; | ||
} | ||
// this is essentially the "update sugar" function from daleharvey/pouchdb#1388 | ||
// the diffFun tells us what delta to apply to the doc. it either returns | ||
// the doc, or false if it doesn't need to do an update after all | ||
function upsert(db, docId, diffFun) { | ||
return new PouchPromise(function (fulfill, reject) { | ||
db.get(docId, function (err, doc) { | ||
if (err) { | ||
/* istanbul ignore next */ | ||
if (err.status !== 404) { | ||
return reject(err); | ||
} | ||
doc = {}; | ||
} | ||
// the user might change the _rev, so save it for posterity | ||
var docRev = doc._rev; | ||
var newDoc = diffFun(doc); | ||
if (!newDoc) { | ||
// if the diffFun returns falsy, we short-circuit as | ||
// an optimization | ||
return fulfill({updated: false, rev: docRev}); | ||
} | ||
// users aren't allowed to modify these values, | ||
// so reset them here | ||
newDoc._id = docId; | ||
newDoc._rev = docRev; | ||
fulfill(tryAndPut(db, newDoc, diffFun)); | ||
}); | ||
}); | ||
} | ||
function tryAndPut(db, doc, diffFun) { | ||
return db.put(doc).then(function (res) { | ||
return { | ||
updated: true, | ||
rev: res.rev | ||
}; | ||
}, function (err) { | ||
/* istanbul ignore next */ | ||
if (err.status !== 409) { | ||
throw err; | ||
} | ||
return upsert(db, doc._id, diffFun); | ||
}); | ||
} | ||
// BEGIN Math.uuid.js | ||
/*! | ||
Math.uuid.js (v1.4) | ||
http://www.broofa.com | ||
mailto:robert@broofa.com | ||
Copyright (c) 2010 Robert Kieffer | ||
Dual licensed under the MIT and GPL licenses. | ||
*/ | ||
/* | ||
* Generate a random uuid. | ||
* | ||
* USAGE: Math.uuid(length, radix) | ||
* length - the desired number of characters | ||
* radix - the number of allowable values for each character. | ||
* | ||
* EXAMPLES: | ||
* // No arguments - returns RFC4122, version 4 ID | ||
* >>> Math.uuid() | ||
* "92329D39-6F5C-4520-ABFC-AAB64544E172" | ||
* | ||
* // One argument - returns ID of the specified length | ||
* >>> Math.uuid(15) // 15 character ID (default base=62) | ||
* "VcydxgltxrVZSTV" | ||
* | ||
* // Two arguments - returns ID of the specified length, and radix. | ||
* // (Radix must be <= 62) | ||
* >>> Math.uuid(8, 2) // 8 character ID (base=2) | ||
* "01001010" | ||
* >>> Math.uuid(8, 10) // 8 character ID (base=10) | ||
* "47473046" | ||
* >>> Math.uuid(8, 16) // 8 character ID (base=16) | ||
* "098F4D35" | ||
*/ | ||
var chars = ( | ||
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + | ||
'abcdefghijklmnopqrstuvwxyz' | ||
).split(''); | ||
var atob$1 = function (str) { | ||
return atob(str); | ||
}; | ||
// Abstracts constructing a Blob object, so it also works in older | ||
// browsers that don't support the native Blob constructor (e.g. | ||
// old QtWebKit versions, Android < 4.4). | ||
function createBlob(parts, properties) { | ||
/* global BlobBuilder,MSBlobBuilder,MozBlobBuilder,WebKitBlobBuilder */ | ||
parts = parts || []; | ||
properties = properties || {}; | ||
try { | ||
return new Blob(parts, properties); | ||
} catch (e) { | ||
if (e.name !== "TypeError") { | ||
throw e; | ||
} | ||
var Builder = typeof BlobBuilder !== 'undefined' ? BlobBuilder : | ||
typeof MSBlobBuilder !== 'undefined' ? MSBlobBuilder : | ||
typeof MozBlobBuilder !== 'undefined' ? MozBlobBuilder : | ||
WebKitBlobBuilder; | ||
var builder = new Builder(); | ||
for (var i = 0; i < parts.length; i += 1) { | ||
builder.append(parts[i]); | ||
} | ||
return builder.getBlob(properties.type); | ||
} | ||
} | ||
// From http://stackoverflow.com/questions/14967647/ (continues on next line) | ||
// encode-decode-image-with-base64-breaks-image (2013-04-21) | ||
function binaryStringToArrayBuffer(bin) { | ||
var length = bin.length; | ||
var buf = new ArrayBuffer(length); | ||
var arr = new Uint8Array(buf); | ||
for (var i = 0; i < length; i++) { | ||
arr[i] = bin.charCodeAt(i); | ||
} | ||
return buf; | ||
} | ||
function binStringToBluffer(binString, type) { | ||
return createBlob([binaryStringToArrayBuffer(binString)], {type: type}); | ||
} | ||
function b64ToBluffer(b64, type) { | ||
return binStringToBluffer(atob$1(b64), type); | ||
} | ||
function pad(str, padWith, upToLength) { | ||
var padding = ''; | ||
var targetLength = upToLength - str.length; | ||
/* istanbul ignore next */ | ||
while (padding.length < targetLength) { | ||
padding += padWith; | ||
} | ||
return padding; | ||
} | ||
function padLeft(str, padWith, upToLength) { | ||
var padding = pad(str, padWith, upToLength); | ||
return padding + str; | ||
} | ||
var MIN_MAGNITUDE = -324; // verified by -Number.MIN_VALUE | ||
var MAGNITUDE_DIGITS = 3; // ditto | ||
var SEP = ''; // set to '_' for easier debugging | ||
function collate(a, b) { | ||
if (a === b) { | ||
return 0; | ||
} | ||
a = normalizeKey(a); | ||
b = normalizeKey(b); | ||
var ai = collationIndex(a); | ||
var bi = collationIndex(b); | ||
if ((ai - bi) !== 0) { | ||
return ai - bi; | ||
} | ||
if (a === null) { | ||
return 0; | ||
} | ||
switch (typeof a) { | ||
case 'number': | ||
return a - b; | ||
case 'boolean': | ||
return a === b ? 0 : (a < b ? -1 : 1); | ||
case 'string': | ||
return stringCollate(a, b); | ||
} | ||
return Array.isArray(a) ? arrayCollate(a, b) : objectCollate(a, b); | ||
} | ||
// couch considers null/NaN/Infinity/-Infinity === undefined, | ||
// for the purposes of mapreduce indexes. also, dates get stringified. | ||
function normalizeKey(key) { | ||
switch (typeof key) { | ||
case 'undefined': | ||
return null; | ||
case 'number': | ||
if (key === Infinity || key === -Infinity || isNaN(key)) { | ||
return null; | ||
} | ||
return key; | ||
case 'object': | ||
var origKey = key; | ||
if (Array.isArray(key)) { | ||
var len = key.length; | ||
key = new Array(len); | ||
for (var i = 0; i < len; i++) { | ||
key[i] = normalizeKey(origKey[i]); | ||
} | ||
/* istanbul ignore next */ | ||
} else if (key instanceof Date) { | ||
return key.toJSON(); | ||
} else if (key !== null) { // generic object | ||
key = {}; | ||
for (var k in origKey) { | ||
if (origKey.hasOwnProperty(k)) { | ||
var val = origKey[k]; | ||
if (typeof val !== 'undefined') { | ||
key[k] = normalizeKey(val); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return key; | ||
} | ||
function indexify(key) { | ||
if (key !== null) { | ||
switch (typeof key) { | ||
case 'boolean': | ||
return key ? 1 : 0; | ||
case 'number': | ||
return numToIndexableString(key); | ||
case 'string': | ||
// We've to be sure that key does not contain \u0000 | ||
// Do order-preserving replacements: | ||
// 0 -> 1, 1 | ||
// 1 -> 1, 2 | ||
// 2 -> 2, 2 | ||
return key | ||
.replace(/\u0002/g, '\u0002\u0002') | ||
.replace(/\u0001/g, '\u0001\u0002') | ||
.replace(/\u0000/g, '\u0001\u0001'); | ||
case 'object': | ||
var isArray = Array.isArray(key); | ||
var arr = isArray ? key : Object.keys(key); | ||
var i = -1; | ||
var len = arr.length; | ||
var result = ''; | ||
if (isArray) { | ||
while (++i < len) { | ||
result += toIndexableString(arr[i]); | ||
} | ||
} else { | ||
while (++i < len) { | ||
var objKey = arr[i]; | ||
result += toIndexableString(objKey) + | ||
toIndexableString(key[objKey]); | ||
} | ||
} | ||
return result; | ||
} | ||
} | ||
return ''; | ||
} | ||
// convert the given key to a string that would be appropriate | ||
// for lexical sorting, e.g. within a database, where the | ||
// sorting is the same given by the collate() function. | ||
function toIndexableString(key) { | ||
var zero = '\u0000'; | ||
key = normalizeKey(key); | ||
return collationIndex(key) + SEP + indexify(key) + zero; | ||
} | ||
function parseNumber(str, i) { | ||
var originalIdx = i; | ||
var num; | ||
var zero = str[i] === '1'; | ||
if (zero) { | ||
num = 0; | ||
i++; | ||
} else { | ||
var neg = str[i] === '0'; | ||
i++; | ||
var numAsString = ''; | ||
var magAsString = str.substring(i, i + MAGNITUDE_DIGITS); | ||
var magnitude = parseInt(magAsString, 10) + MIN_MAGNITUDE; | ||
/* istanbul ignore next */ | ||
if (neg) { | ||
magnitude = -magnitude; | ||
} | ||
i += MAGNITUDE_DIGITS; | ||
while (true) { | ||
var ch = str[i]; | ||
if (ch === '\u0000') { | ||
break; | ||
} else { | ||
numAsString += ch; | ||
} | ||
i++; | ||
} | ||
numAsString = numAsString.split('.'); | ||
if (numAsString.length === 1) { | ||
num = parseInt(numAsString, 10); | ||
} else { | ||
/* istanbul ignore next */ | ||
num = parseFloat(numAsString[0] + '.' + numAsString[1]); | ||
} | ||
/* istanbul ignore next */ | ||
if (neg) { | ||
num = num - 10; | ||
} | ||
/* istanbul ignore next */ | ||
if (magnitude !== 0) { | ||
// parseFloat is more reliable than pow due to rounding errors | ||
// e.g. Number.MAX_VALUE would return Infinity if we did | ||
// num * Math.pow(10, magnitude); | ||
num = parseFloat(num + 'e' + magnitude); | ||
} | ||
} | ||
return {num: num, length : i - originalIdx}; | ||
} | ||
// move up the stack while parsing | ||
// this function moved outside of parseIndexableString for performance | ||
function pop(stack, metaStack) { | ||
var obj = stack.pop(); | ||
if (metaStack.length) { | ||
var lastMetaElement = metaStack[metaStack.length - 1]; | ||
if (obj === lastMetaElement.element) { | ||
// popping a meta-element, e.g. an object whose value is another object | ||
metaStack.pop(); | ||
lastMetaElement = metaStack[metaStack.length - 1]; | ||
} | ||
var element = lastMetaElement.element; | ||
var lastElementIndex = lastMetaElement.index; | ||
if (Array.isArray(element)) { | ||
element.push(obj); | ||
} else if (lastElementIndex === stack.length - 2) { // obj with key+value | ||
var key = stack.pop(); | ||
element[key] = obj; | ||
} else { | ||
stack.push(obj); // obj with key only | ||
} | ||
} | ||
} | ||
function parseIndexableString(str) { | ||
var stack = []; | ||
var metaStack = []; // stack for arrays and objects | ||
var i = 0; | ||
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ | ||
while (true) { | ||
var collationIndex = str[i++]; | ||
if (collationIndex === '\u0000') { | ||
if (stack.length === 1) { | ||
return stack.pop(); | ||
} else { | ||
pop(stack, metaStack); | ||
continue; | ||
} | ||
} | ||
switch (collationIndex) { | ||
case '1': | ||
stack.push(null); | ||
break; | ||
case '2': | ||
stack.push(str[i] === '1'); | ||
i++; | ||
break; | ||
case '3': | ||
var parsedNum = parseNumber(str, i); | ||
stack.push(parsedNum.num); | ||
i += parsedNum.length; | ||
break; | ||
case '4': | ||
var parsedStr = ''; | ||
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ | ||
while (true) { | ||
var ch = str[i]; | ||
if (ch === '\u0000') { | ||
break; | ||
} | ||
parsedStr += ch; | ||
i++; | ||
} | ||
// perform the reverse of the order-preserving replacement | ||
// algorithm (see above) | ||
parsedStr = parsedStr.replace(/\u0001\u0001/g, '\u0000') | ||
.replace(/\u0001\u0002/g, '\u0001') | ||
.replace(/\u0002\u0002/g, '\u0002'); | ||
stack.push(parsedStr); | ||
break; | ||
case '5': | ||
var arrayElement = { element: [], index: stack.length }; | ||
stack.push(arrayElement.element); | ||
metaStack.push(arrayElement); | ||
break; | ||
case '6': | ||
var objElement = { element: {}, index: stack.length }; | ||
stack.push(objElement.element); | ||
metaStack.push(objElement); | ||
break; | ||
/* istanbul ignore next */ | ||
default: | ||
throw new Error( | ||
'bad collationIndex or unexpectedly reached end of input: ' + | ||
collationIndex); | ||
} | ||
} | ||
} | ||
function arrayCollate(a, b) { | ||
var len = Math.min(a.length, b.length); | ||
for (var i = 0; i < len; i++) { | ||
var sort = collate(a[i], b[i]); | ||
if (sort !== 0) { | ||
return sort; | ||
} | ||
} | ||
return (a.length === b.length) ? 0 : | ||
(a.length > b.length) ? 1 : -1; | ||
} | ||
function stringCollate(a, b) { | ||
// See: https://github.com/daleharvey/pouchdb/issues/40 | ||
// This is incompatible with the CouchDB implementation, but its the | ||
// best we can do for now | ||
return (a === b) ? 0 : ((a > b) ? 1 : -1); | ||
} | ||
function objectCollate(a, b) { | ||
var ak = Object.keys(a), bk = Object.keys(b); | ||
var len = Math.min(ak.length, bk.length); | ||
for (var i = 0; i < len; i++) { | ||
// First sort the keys | ||
var sort = collate(ak[i], bk[i]); | ||
if (sort !== 0) { | ||
return sort; | ||
} | ||
// if the keys are equal sort the values | ||
sort = collate(a[ak[i]], b[bk[i]]); | ||
if (sort !== 0) { | ||
return sort; | ||
} | ||
} | ||
return (ak.length === bk.length) ? 0 : | ||
(ak.length > bk.length) ? 1 : -1; | ||
} | ||
// The collation is defined by erlangs ordered terms | ||
// the atoms null, true, false come first, then numbers, strings, | ||
// arrays, then objects | ||
// null/undefined/NaN/Infinity/-Infinity are all considered null | ||
function collationIndex(x) { | ||
var id = ['boolean', 'number', 'string', 'object']; | ||
var idx = id.indexOf(typeof x); | ||
//false if -1 otherwise true, but fast!!!!1 | ||
if (~idx) { | ||
if (x === null) { | ||
return 1; | ||
} | ||
if (Array.isArray(x)) { | ||
return 5; | ||
} | ||
return idx < 3 ? (idx + 2) : (idx + 3); | ||
} | ||
/* istanbul ignore next */ | ||
if (Array.isArray(x)) { | ||
return 5; | ||
} | ||
} | ||
// conversion: | ||
// x yyy zz...zz | ||
// x = 0 for negative, 1 for 0, 2 for positive | ||
// y = exponent (for negative numbers negated) moved so that it's >= 0 | ||
// z = mantisse | ||
function numToIndexableString(num) { | ||
if (num === 0) { | ||
return '1'; | ||
} | ||
// convert number to exponential format for easier and | ||
// more succinct string sorting | ||
var expFormat = num.toExponential().split(/e\+?/); | ||
var magnitude = parseInt(expFormat[1], 10); | ||
var neg = num < 0; | ||
var result = neg ? '0' : '2'; | ||
// first sort by magnitude | ||
// it's easier if all magnitudes are positive | ||
var magForComparison = ((neg ? -magnitude : magnitude) - MIN_MAGNITUDE); | ||
var magString = padLeft((magForComparison).toString(), '0', MAGNITUDE_DIGITS); | ||
result += SEP + magString; | ||
// then sort by the factor | ||
var factor = Math.abs(parseFloat(expFormat[0])); // [1..10) | ||
/* istanbul ignore next */ | ||
if (neg) { // for negative reverse ordering | ||
factor = 10 - factor; | ||
} | ||
var factorStr = factor.toFixed(20); | ||
// strip zeros from the end | ||
factorStr = factorStr.replace(/\.?0+$/, ''); | ||
result += SEP + factorStr; | ||
return result; | ||
} | ||
/* | ||
* Simple task queue to sequentialize actions. Assumes | ||
@@ -21,3 +862,3 @@ * callbacks will eventually fire (once). | ||
function TaskQueue() { | ||
this.promise = new Promise(function (fulfill) {fulfill(); }); | ||
this.promise = new PouchPromise(function (fulfill) {fulfill(); }); | ||
} | ||
@@ -36,2 +877,6 @@ TaskQueue.prototype.add = function (promiseFactory) { | ||
function stringMd5(string) { | ||
return Md5.hash(string); | ||
} | ||
function createView(opts) { | ||
@@ -60,3 +905,3 @@ var sourceDB = opts.db; | ||
var depDbName = info.db_name + '-mrview-' + | ||
(temporary ? 'temp' : pouchdbMd5.stringMd5(viewSignature)); | ||
(temporary ? 'temp' : stringMd5(viewSignature)); | ||
@@ -79,3 +924,3 @@ // save the view name in the source db so it can be cleaned up if necessary | ||
} | ||
return pouchdbUtils.upsert(sourceDB, '_local/mrviews', diffFunction).then(function () { | ||
return upsert(sourceDB, '_local/mrviews', diffFunction).then(function () { | ||
return sourceDB.registerDependentDatabase(depDbName).then(function (res) { | ||
@@ -129,2 +974,69 @@ var db = res.db; | ||
var promisedCallback = function (promise, callback) { | ||
if (callback) { | ||
promise.then(function (res) { | ||
process.nextTick(function () { | ||
callback(null, res); | ||
}); | ||
}, function (reason) { | ||
process.nextTick(function () { | ||
callback(reason); | ||
}); | ||
}); | ||
} | ||
return promise; | ||
}; | ||
var callbackify = function (fun) { | ||
return getArguments(function (args) { | ||
var cb = args.pop(); | ||
var promise = fun.apply(this, args); | ||
if (typeof cb === 'function') { | ||
promisedCallback(promise, cb); | ||
} | ||
return promise; | ||
}); | ||
}; | ||
// Promise finally util similar to Q.finally | ||
var fin = function (promise, finalPromiseFactory) { | ||
return promise.then(function (res) { | ||
return finalPromiseFactory().then(function () { | ||
return res; | ||
}); | ||
}, function (reason) { | ||
return finalPromiseFactory().then(function () { | ||
throw reason; | ||
}); | ||
}); | ||
}; | ||
var sequentialize = function (queue, promiseFactory) { | ||
return function () { | ||
var args = arguments; | ||
var that = this; | ||
return queue.add(function () { | ||
return promiseFactory.apply(that, args); | ||
}); | ||
}; | ||
}; | ||
// uniq an array of strings, order not guaranteed | ||
// similar to underscore/lodash _.uniq | ||
var uniq = function (arr) { | ||
var map = {}; | ||
for (var i = 0, len = arr.length; i < len; i++) { | ||
map['$' + arr[i]] = true; | ||
} | ||
var keys = Object.keys(map); | ||
var output = new Array(keys.length); | ||
for (i = 0, len = keys.length; i < len; i++) { | ||
output[i] = keys[i].substring(1); | ||
} | ||
return output; | ||
}; | ||
var persistentQueues = {}; | ||
@@ -134,3 +1046,3 @@ var tempViewQueue = new TaskQueue(); | ||
var log = pouchdbUtils.guardedConsole.bind(null, 'log'); | ||
var log = guardedConsole.bind(null, 'log'); | ||
@@ -153,3 +1065,3 @@ function parseViewName(name) { | ||
} catch (err) { | ||
pouchdbUtils.guardedConsole('error', | ||
guardedConsole('error', | ||
'The user\'s map/reduce function threw an uncaught error.\n' + | ||
@@ -159,3 +1071,3 @@ 'You can debug this error by doing:\n' + | ||
'Please double-check your map/reduce function.'); | ||
pouchdbUtils.guardedConsole('error', e); | ||
guardedConsole('error', e); | ||
} | ||
@@ -178,4 +1090,4 @@ } | ||
function sortByKeyThenValue(x, y) { | ||
var keyCompare = pouchdbCollate.collate(x.key, y.key); | ||
return keyCompare !== 0 ? keyCompare : pouchdbCollate.collate(x.value, y.value); | ||
var keyCompare = collate(x.key, y.key); | ||
return keyCompare !== 0 ? keyCompare : collate(x.value, y.value); | ||
} | ||
@@ -209,3 +1121,3 @@ | ||
var att = atts[filename]; | ||
atts[filename].data = pouchdbBinaryUtils.base64StringToBlobOrBuffer(att.data, att.content_type); | ||
atts[filename].data = b64ToBluffer(att.data, att.content_type); | ||
}); | ||
@@ -340,3 +1252,3 @@ }); | ||
typeof options[endkeyName] !== 'undefined' && | ||
pouchdbCollate.collate(options[startkeyName], options[endkeyName]) > 0) { | ||
collate(options[startkeyName], options[endkeyName]) > 0) { | ||
throw new QueryParseError('No rows can match your key range, ' + | ||
@@ -445,3 +1357,3 @@ 'reverse your start_key and end_key or set {descending : true}'); | ||
function customQuery(db, fun, opts) { | ||
return new Promise(function (resolve, reject) { | ||
return new PouchPromise(function (resolve, reject) { | ||
db._query(fun, opts, function (err, res) { | ||
@@ -460,3 +1372,3 @@ if (err) { | ||
function customViewCleanup(db) { | ||
return new Promise(function (resolve, reject) { | ||
return new PouchPromise(function (resolve, reject) { | ||
db._viewCleanup(function (err, res) { | ||
@@ -496,3 +1408,3 @@ if (err) { | ||
// for performance reasons (avoids unnecessary GETs) | ||
return Promise.resolve(defaultMetaDoc); | ||
return PouchPromise.resolve(defaultMetaDoc); | ||
} | ||
@@ -505,3 +1417,3 @@ return view.db.get(metaDocId).catch(defaultsTo(defaultMetaDoc)); | ||
// no keys, no need for a lookup | ||
return Promise.resolve({rows: []}); | ||
return PouchPromise.resolve({rows: []}); | ||
} | ||
@@ -549,3 +1461,3 @@ return view.db.allDocs({ | ||
}); | ||
metaDoc.keys = pouchdbMapreduceUtils.uniq(newKeys.concat(metaDoc.keys)); | ||
metaDoc.keys = uniq(newKeys.concat(metaDoc.keys)); | ||
kvDocs.push(metaDoc); | ||
@@ -571,6 +1483,6 @@ | ||
var docIds = Object.keys(docIdsToChangesAndEmits); | ||
return Promise.all(docIds.map(function (docId) { | ||
return PouchPromise.all(docIds.map(function (docId) { | ||
return getDocsToPersist(docId, view, docIdsToChangesAndEmits); | ||
})).then(function (listOfDocsToPersist) { | ||
var docsToPersist = pouchdbUtils.flatten(listOfDocsToPersist); | ||
var docsToPersist = flatten(listOfDocsToPersist); | ||
lastSeqDoc.seq = seq; | ||
@@ -594,3 +1506,3 @@ docsToPersist.push(lastSeqDoc); | ||
function updateView(view) { | ||
return pouchdbMapreduceUtils.sequentialize(getQueue(view), function () { | ||
return sequentialize(getQueue(view), function () { | ||
return updateViewInQueue(view); | ||
@@ -606,7 +1518,7 @@ })(); | ||
function emit(key, value) { | ||
var output = {id: doc._id, key: pouchdbCollate.normalizeKey(key)}; | ||
var output = {id: doc._id, key: normalizeKey(key)}; | ||
// Don't explicitly store the value unless it's defined and non-null. | ||
// This saves on storage space, because often people don't use it. | ||
if (typeof value !== 'undefined' && value !== null) { | ||
output.value = pouchdbCollate.normalizeKey(value); | ||
output.value = normalizeKey(value); | ||
} | ||
@@ -639,3 +1551,3 @@ mapResults.push(output); | ||
return new Promise(function (resolve, reject) { | ||
return new PouchPromise(function (resolve, reject) { | ||
@@ -678,6 +1590,6 @@ function complete() { | ||
var complexKey = [obj.key, obj.id]; | ||
if (pouchdbCollate.collate(obj.key, lastKey) === 0) { | ||
if (collate(obj.key, lastKey) === 0) { | ||
complexKey.push(j); // dup key+id, so make it unique | ||
} | ||
var indexableKey = pouchdbCollate.toIndexableString(complexKey); | ||
var indexableKey = toIndexableString(complexKey); | ||
indexableKeysToKeyValues[indexableKey] = obj; | ||
@@ -736,3 +1648,3 @@ lastKey = obj.key; | ||
if (last && pouchdbCollate.collate(last.groupKey, groupKey) === 0) { | ||
if (last && collate(last.groupKey, groupKey) === 0) { | ||
last.keys.push([e.key, e.id]); | ||
@@ -768,3 +1680,3 @@ last.values.push(e.value); | ||
function queryView(view, opts) { | ||
return pouchdbMapreduceUtils.sequentialize(getQueue(view), function () { | ||
return sequentialize(getQueue(view), function () { | ||
return queryViewInQueue(view, opts); | ||
@@ -805,3 +1717,3 @@ })(); | ||
var parsedKeyAndDocId = pouchdbCollate.parseIndexableString(result.doc._id); | ||
var parsedKeyAndDocId = parseIndexableString(result.doc._id); | ||
return { | ||
@@ -828,3 +1740,3 @@ key: parsedKeyAndDocId[0], | ||
if (opts.include_docs) { | ||
var docIds = pouchdbMapreduceUtils.uniq(rows.map(rowToDocId)); | ||
var docIds = uniq(rows.map(rowToDocId)); | ||
@@ -862,8 +1774,8 @@ return view.sourceDB.allDocs({ | ||
var viewOpts = { | ||
startkey : pouchdbCollate.toIndexableString([key]), | ||
endkey : pouchdbCollate.toIndexableString([key, {}]) | ||
startkey : toIndexableString([key]), | ||
endkey : toIndexableString([key, {}]) | ||
}; | ||
return fetchFromView(viewOpts); | ||
}); | ||
return Promise.all(fetchPromises).then(pouchdbUtils.flatten).then(onMapResultsReady); | ||
return PouchPromise.all(fetchPromises).then(flatten).then(onMapResultsReady); | ||
} else { // normal query, no 'keys' | ||
@@ -881,4 +1793,4 @@ var viewOpts = { | ||
viewOpts.startkey = opts.descending ? | ||
pouchdbCollate.toIndexableString([opts.startkey, {}]) : | ||
pouchdbCollate.toIndexableString([opts.startkey]); | ||
toIndexableString([opts.startkey, {}]) : | ||
toIndexableString([opts.startkey]); | ||
} | ||
@@ -891,8 +1803,8 @@ if (typeof opts.endkey !== 'undefined') { | ||
viewOpts.endkey = pouchdbCollate.toIndexableString( | ||
viewOpts.endkey = toIndexableString( | ||
inclusiveEnd ? [opts.endkey, {}] : [opts.endkey]); | ||
} | ||
if (typeof opts.key !== 'undefined') { | ||
var keyStart = pouchdbCollate.toIndexableString([opts.key]); | ||
var keyEnd = pouchdbCollate.toIndexableString([opts.key, {}]); | ||
var keyStart = toIndexableString([opts.key]); | ||
var keyEnd = toIndexableString([opts.key, {}]); | ||
if (viewOpts.descending) { | ||
@@ -962,7 +1874,7 @@ viewOpts.endkey = keyStart; | ||
var destroyPromises = dbsToDelete.map(function (viewDBName) { | ||
return pouchdbMapreduceUtils.sequentialize(getQueue(viewDBName), function () { | ||
return sequentialize(getQueue(viewDBName), function () { | ||
return new db.constructor(viewDBName, db.__opts).destroy(); | ||
})(); | ||
}); | ||
return Promise.all(destroyPromises).then(function () { | ||
return PouchPromise.all(destroyPromises).then(function () { | ||
return {ok: true}; | ||
@@ -974,3 +1886,3 @@ }); | ||
var viewCleanup = pouchdbMapreduceUtils.callbackify(function () { | ||
var viewCleanup = callbackify(function () { | ||
var db = this; | ||
@@ -1013,3 +1925,3 @@ if (db.type() === 'http') { | ||
} | ||
return pouchdbMapreduceUtils.fin(updateView(view).then(function () { | ||
return fin(updateView(view).then(function () { | ||
return queryView(view, opts); | ||
@@ -1071,6 +1983,6 @@ }), cleanup); | ||
var db = this; | ||
var promise = Promise.resolve().then(function () { | ||
var promise = PouchPromise.resolve().then(function () { | ||
return queryPromised(db, fun, opts); | ||
}); | ||
pouchdbMapreduceUtils.promisedCallback(promise, callback); | ||
promisedCallback(promise, callback); | ||
return promise; | ||
@@ -1077,0 +1989,0 @@ }; |
957
lib/index.js
@@ -5,12 +5,802 @@ 'use strict'; | ||
var pouchdbUtils = require('pouchdb-utils'); | ||
var pouchdbBinaryUtils = require('pouchdb-binary-utils'); | ||
var pouchdbCollate = require('pouchdb-collate'); | ||
var Promise = _interopDefault(require('pouchdb-promise')); | ||
var pouchdbMd5 = require('pouchdb-md5'); | ||
var lie = _interopDefault(require('lie')); | ||
var getArguments = _interopDefault(require('argsarray')); | ||
var debug = _interopDefault(require('debug')); | ||
var events = require('events'); | ||
var inherits = _interopDefault(require('inherits')); | ||
var crypto = _interopDefault(require('crypto')); | ||
var scopedEval = _interopDefault(require('scope-eval')); | ||
var pouchdbMapreduceUtils = require('pouchdb-mapreduce-utils'); | ||
var inherits = _interopDefault(require('inherits')); | ||
/* istanbul ignore next */ | ||
var PouchPromise = typeof Promise === 'function' ? Promise : lie; | ||
// most of this is borrowed from lodash.isPlainObject: | ||
// https://github.com/fis-components/lodash.isplainobject/ | ||
// blob/29c358140a74f252aeb08c9eb28bef86f2217d4a/index.js | ||
var funcToString = Function.prototype.toString; | ||
var objectCtorString = funcToString.call(Object); | ||
var log$1 = debug('pouchdb:api'); | ||
// like underscore/lodash _.pick() | ||
function pick(obj, arr) { | ||
var res = {}; | ||
for (var i = 0, len = arr.length; i < len; i++) { | ||
var prop = arr[i]; | ||
if (prop in obj) { | ||
res[prop] = obj[prop]; | ||
} | ||
} | ||
return res; | ||
} | ||
// in Node of course this is false | ||
function isChromeApp() { | ||
return false; | ||
} | ||
// in Node of course this is false | ||
function hasLocalStorage() { | ||
return false; | ||
} | ||
inherits(Changes, events.EventEmitter); | ||
/* istanbul ignore next */ | ||
function attachBrowserEvents(self) { | ||
if (isChromeApp()) { | ||
chrome.storage.onChanged.addListener(function (e) { | ||
// make sure it's event addressed to us | ||
if (e.db_name != null) { | ||
//object only has oldValue, newValue members | ||
self.emit(e.dbName.newValue); | ||
} | ||
}); | ||
} else if (hasLocalStorage()) { | ||
if (typeof addEventListener !== 'undefined') { | ||
addEventListener("storage", function (e) { | ||
self.emit(e.key); | ||
}); | ||
} else { // old IE | ||
window.attachEvent("storage", function (e) { | ||
self.emit(e.key); | ||
}); | ||
} | ||
} | ||
} | ||
function Changes() { | ||
events.EventEmitter.call(this); | ||
this._listeners = {}; | ||
attachBrowserEvents(this); | ||
} | ||
Changes.prototype.addListener = function (dbName, id, db, opts) { | ||
/* istanbul ignore if */ | ||
if (this._listeners[id]) { | ||
return; | ||
} | ||
var self = this; | ||
var inprogress = false; | ||
function eventFunction() { | ||
/* istanbul ignore if */ | ||
if (!self._listeners[id]) { | ||
return; | ||
} | ||
if (inprogress) { | ||
inprogress = 'waiting'; | ||
return; | ||
} | ||
inprogress = true; | ||
var changesOpts = pick(opts, [ | ||
'style', 'include_docs', 'attachments', 'conflicts', 'filter', | ||
'doc_ids', 'view', 'since', 'query_params', 'binary' | ||
]); | ||
/* istanbul ignore next */ | ||
function onError() { | ||
inprogress = false; | ||
} | ||
db.changes(changesOpts).on('change', function (c) { | ||
if (c.seq > opts.since && !opts.cancelled) { | ||
opts.since = c.seq; | ||
opts.onChange(c); | ||
} | ||
}).on('complete', function () { | ||
if (inprogress === 'waiting') { | ||
setTimeout(function (){ | ||
eventFunction(); | ||
},0); | ||
} | ||
inprogress = false; | ||
}).on('error', onError); | ||
} | ||
this._listeners[id] = eventFunction; | ||
this.on(dbName, eventFunction); | ||
}; | ||
Changes.prototype.removeListener = function (dbName, id) { | ||
/* istanbul ignore if */ | ||
if (!(id in this._listeners)) { | ||
return; | ||
} | ||
events.EventEmitter.prototype.removeListener.call(this, dbName, | ||
this._listeners[id]); | ||
delete this._listeners[id]; | ||
}; | ||
/* istanbul ignore next */ | ||
Changes.prototype.notifyLocalWindows = function (dbName) { | ||
//do a useless change on a storage thing | ||
//in order to get other windows's listeners to activate | ||
if (isChromeApp()) { | ||
chrome.storage.local.set({dbName: dbName}); | ||
} else if (hasLocalStorage()) { | ||
localStorage[dbName] = (localStorage[dbName] === "a") ? "b" : "a"; | ||
} | ||
}; | ||
Changes.prototype.notify = function (dbName) { | ||
this.emit(dbName); | ||
this.notifyLocalWindows(dbName); | ||
}; | ||
function guardedConsole(method) { | ||
/* istanbul ignore else */ | ||
if (console !== 'undefined' && method in console) { | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
console[method].apply(console, args); | ||
} | ||
} | ||
inherits(PouchError, Error); | ||
function PouchError(opts) { | ||
Error.call(this, opts.reason); | ||
this.status = opts.status; | ||
this.name = opts.error; | ||
this.message = opts.reason; | ||
this.error = true; | ||
} | ||
PouchError.prototype.toString = function () { | ||
return JSON.stringify({ | ||
status: this.status, | ||
name: this.name, | ||
message: this.message, | ||
reason: this.reason | ||
}); | ||
}; | ||
var UNAUTHORIZED = new PouchError({ | ||
status: 401, | ||
error: 'unauthorized', | ||
reason: "Name or password is incorrect." | ||
}); | ||
var MISSING_BULK_DOCS = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: "Missing JSON list of 'docs'" | ||
}); | ||
var MISSING_DOC = new PouchError({ | ||
status: 404, | ||
error: 'not_found', | ||
reason: 'missing' | ||
}); | ||
var REV_CONFLICT = new PouchError({ | ||
status: 409, | ||
error: 'conflict', | ||
reason: 'Document update conflict' | ||
}); | ||
var INVALID_ID = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: '_id field must contain a string' | ||
}); | ||
var MISSING_ID = new PouchError({ | ||
status: 412, | ||
error: 'missing_id', | ||
reason: '_id is required for puts' | ||
}); | ||
var RESERVED_ID = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: 'Only reserved document ids may start with underscore.' | ||
}); | ||
var NOT_OPEN = new PouchError({ | ||
status: 412, | ||
error: 'precondition_failed', | ||
reason: 'Database not open' | ||
}); | ||
var UNKNOWN_ERROR = new PouchError({ | ||
status: 500, | ||
error: 'unknown_error', | ||
reason: 'Database encountered an unknown error' | ||
}); | ||
var BAD_ARG = new PouchError({ | ||
status: 500, | ||
error: 'badarg', | ||
reason: 'Some query argument is invalid' | ||
}); | ||
var INVALID_REQUEST = new PouchError({ | ||
status: 400, | ||
error: 'invalid_request', | ||
reason: 'Request was invalid' | ||
}); | ||
var QUERY_PARSE_ERROR = new PouchError({ | ||
status: 400, | ||
error: 'query_parse_error', | ||
reason: 'Some query parameter is invalid' | ||
}); | ||
var DOC_VALIDATION = new PouchError({ | ||
status: 500, | ||
error: 'doc_validation', | ||
reason: 'Bad special document member' | ||
}); | ||
var BAD_REQUEST = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: 'Something wrong with the request' | ||
}); | ||
var NOT_AN_OBJECT = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: 'Document must be a JSON object' | ||
}); | ||
var DB_MISSING = new PouchError({ | ||
status: 404, | ||
error: 'not_found', | ||
reason: 'Database not found' | ||
}); | ||
var IDB_ERROR = new PouchError({ | ||
status: 500, | ||
error: 'indexed_db_went_bad', | ||
reason: 'unknown' | ||
}); | ||
var WSQ_ERROR = new PouchError({ | ||
status: 500, | ||
error: 'web_sql_went_bad', | ||
reason: 'unknown' | ||
}); | ||
var LDB_ERROR = new PouchError({ | ||
status: 500, | ||
error: 'levelDB_went_went_bad', | ||
reason: 'unknown' | ||
}); | ||
var FORBIDDEN = new PouchError({ | ||
status: 403, | ||
error: 'forbidden', | ||
reason: 'Forbidden by design doc validate_doc_update function' | ||
}); | ||
var INVALID_REV = new PouchError({ | ||
status: 400, | ||
error: 'bad_request', | ||
reason: 'Invalid rev format' | ||
}); | ||
var FILE_EXISTS = new PouchError({ | ||
status: 412, | ||
error: 'file_exists', | ||
reason: 'The database could not be created, the file already exists.' | ||
}); | ||
var MISSING_STUB = new PouchError({ | ||
status: 412, | ||
error: 'missing_stub' | ||
}); | ||
var INVALID_URL = new PouchError({ | ||
status: 413, | ||
error: 'invalid_url', | ||
reason: 'Provided URL is invalid' | ||
}); | ||
function flatten(arrs) { | ||
var res = []; | ||
for (var i = 0, len = arrs.length; i < len; i++) { | ||
res = res.concat(arrs[i]); | ||
} | ||
return res; | ||
} | ||
// this is essentially the "update sugar" function from daleharvey/pouchdb#1388 | ||
// the diffFun tells us what delta to apply to the doc. it either returns | ||
// the doc, or false if it doesn't need to do an update after all | ||
function upsert(db, docId, diffFun) { | ||
return new PouchPromise(function (fulfill, reject) { | ||
db.get(docId, function (err, doc) { | ||
if (err) { | ||
/* istanbul ignore next */ | ||
if (err.status !== 404) { | ||
return reject(err); | ||
} | ||
doc = {}; | ||
} | ||
// the user might change the _rev, so save it for posterity | ||
var docRev = doc._rev; | ||
var newDoc = diffFun(doc); | ||
if (!newDoc) { | ||
// if the diffFun returns falsy, we short-circuit as | ||
// an optimization | ||
return fulfill({updated: false, rev: docRev}); | ||
} | ||
// users aren't allowed to modify these values, | ||
// so reset them here | ||
newDoc._id = docId; | ||
newDoc._rev = docRev; | ||
fulfill(tryAndPut(db, newDoc, diffFun)); | ||
}); | ||
}); | ||
} | ||
function tryAndPut(db, doc, diffFun) { | ||
return db.put(doc).then(function (res) { | ||
return { | ||
updated: true, | ||
rev: res.rev | ||
}; | ||
}, function (err) { | ||
/* istanbul ignore next */ | ||
if (err.status !== 409) { | ||
throw err; | ||
} | ||
return upsert(db, doc._id, diffFun); | ||
}); | ||
} | ||
// BEGIN Math.uuid.js | ||
/*! | ||
Math.uuid.js (v1.4) | ||
http://www.broofa.com | ||
mailto:robert@broofa.com | ||
Copyright (c) 2010 Robert Kieffer | ||
Dual licensed under the MIT and GPL licenses. | ||
*/ | ||
/* | ||
* Generate a random uuid. | ||
* | ||
* USAGE: Math.uuid(length, radix) | ||
* length - the desired number of characters | ||
* radix - the number of allowable values for each character. | ||
* | ||
* EXAMPLES: | ||
* // No arguments - returns RFC4122, version 4 ID | ||
* >>> Math.uuid() | ||
* "92329D39-6F5C-4520-ABFC-AAB64544E172" | ||
* | ||
* // One argument - returns ID of the specified length | ||
* >>> Math.uuid(15) // 15 character ID (default base=62) | ||
* "VcydxgltxrVZSTV" | ||
* | ||
* // Two arguments - returns ID of the specified length, and radix. | ||
* // (Radix must be <= 62) | ||
* >>> Math.uuid(8, 2) // 8 character ID (base=2) | ||
* "01001010" | ||
* >>> Math.uuid(8, 10) // 8 character ID (base=10) | ||
* "47473046" | ||
* >>> Math.uuid(8, 16) // 8 character ID (base=16) | ||
* "098F4D35" | ||
*/ | ||
var chars = ( | ||
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + | ||
'abcdefghijklmnopqrstuvwxyz' | ||
).split(''); | ||
function typedBuffer(binString, buffType, type) { | ||
// buffType is either 'binary' or 'base64' | ||
var buff = new Buffer(binString, buffType); | ||
buff.type = type; // non-standard, but used for consistency with the browser | ||
return buff; | ||
} | ||
function b64ToBluffer(b64, type) { | ||
return typedBuffer(b64, 'base64', type); | ||
} | ||
function pad(str, padWith, upToLength) { | ||
var padding = ''; | ||
var targetLength = upToLength - str.length; | ||
/* istanbul ignore next */ | ||
while (padding.length < targetLength) { | ||
padding += padWith; | ||
} | ||
return padding; | ||
} | ||
function padLeft(str, padWith, upToLength) { | ||
var padding = pad(str, padWith, upToLength); | ||
return padding + str; | ||
} | ||
var MIN_MAGNITUDE = -324; // verified by -Number.MIN_VALUE | ||
var MAGNITUDE_DIGITS = 3; // ditto | ||
var SEP = ''; // set to '_' for easier debugging | ||
function collate(a, b) { | ||
if (a === b) { | ||
return 0; | ||
} | ||
a = normalizeKey(a); | ||
b = normalizeKey(b); | ||
var ai = collationIndex(a); | ||
var bi = collationIndex(b); | ||
if ((ai - bi) !== 0) { | ||
return ai - bi; | ||
} | ||
if (a === null) { | ||
return 0; | ||
} | ||
switch (typeof a) { | ||
case 'number': | ||
return a - b; | ||
case 'boolean': | ||
return a === b ? 0 : (a < b ? -1 : 1); | ||
case 'string': | ||
return stringCollate(a, b); | ||
} | ||
return Array.isArray(a) ? arrayCollate(a, b) : objectCollate(a, b); | ||
} | ||
// couch considers null/NaN/Infinity/-Infinity === undefined, | ||
// for the purposes of mapreduce indexes. also, dates get stringified. | ||
function normalizeKey(key) { | ||
switch (typeof key) { | ||
case 'undefined': | ||
return null; | ||
case 'number': | ||
if (key === Infinity || key === -Infinity || isNaN(key)) { | ||
return null; | ||
} | ||
return key; | ||
case 'object': | ||
var origKey = key; | ||
if (Array.isArray(key)) { | ||
var len = key.length; | ||
key = new Array(len); | ||
for (var i = 0; i < len; i++) { | ||
key[i] = normalizeKey(origKey[i]); | ||
} | ||
/* istanbul ignore next */ | ||
} else if (key instanceof Date) { | ||
return key.toJSON(); | ||
} else if (key !== null) { // generic object | ||
key = {}; | ||
for (var k in origKey) { | ||
if (origKey.hasOwnProperty(k)) { | ||
var val = origKey[k]; | ||
if (typeof val !== 'undefined') { | ||
key[k] = normalizeKey(val); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return key; | ||
} | ||
function indexify(key) { | ||
if (key !== null) { | ||
switch (typeof key) { | ||
case 'boolean': | ||
return key ? 1 : 0; | ||
case 'number': | ||
return numToIndexableString(key); | ||
case 'string': | ||
// We've to be sure that key does not contain \u0000 | ||
// Do order-preserving replacements: | ||
// 0 -> 1, 1 | ||
// 1 -> 1, 2 | ||
// 2 -> 2, 2 | ||
return key | ||
.replace(/\u0002/g, '\u0002\u0002') | ||
.replace(/\u0001/g, '\u0001\u0002') | ||
.replace(/\u0000/g, '\u0001\u0001'); | ||
case 'object': | ||
var isArray = Array.isArray(key); | ||
var arr = isArray ? key : Object.keys(key); | ||
var i = -1; | ||
var len = arr.length; | ||
var result = ''; | ||
if (isArray) { | ||
while (++i < len) { | ||
result += toIndexableString(arr[i]); | ||
} | ||
} else { | ||
while (++i < len) { | ||
var objKey = arr[i]; | ||
result += toIndexableString(objKey) + | ||
toIndexableString(key[objKey]); | ||
} | ||
} | ||
return result; | ||
} | ||
} | ||
return ''; | ||
} | ||
// convert the given key to a string that would be appropriate | ||
// for lexical sorting, e.g. within a database, where the | ||
// sorting is the same given by the collate() function. | ||
function toIndexableString(key) { | ||
var zero = '\u0000'; | ||
key = normalizeKey(key); | ||
return collationIndex(key) + SEP + indexify(key) + zero; | ||
} | ||
function parseNumber(str, i) { | ||
var originalIdx = i; | ||
var num; | ||
var zero = str[i] === '1'; | ||
if (zero) { | ||
num = 0; | ||
i++; | ||
} else { | ||
var neg = str[i] === '0'; | ||
i++; | ||
var numAsString = ''; | ||
var magAsString = str.substring(i, i + MAGNITUDE_DIGITS); | ||
var magnitude = parseInt(magAsString, 10) + MIN_MAGNITUDE; | ||
/* istanbul ignore next */ | ||
if (neg) { | ||
magnitude = -magnitude; | ||
} | ||
i += MAGNITUDE_DIGITS; | ||
while (true) { | ||
var ch = str[i]; | ||
if (ch === '\u0000') { | ||
break; | ||
} else { | ||
numAsString += ch; | ||
} | ||
i++; | ||
} | ||
numAsString = numAsString.split('.'); | ||
if (numAsString.length === 1) { | ||
num = parseInt(numAsString, 10); | ||
} else { | ||
/* istanbul ignore next */ | ||
num = parseFloat(numAsString[0] + '.' + numAsString[1]); | ||
} | ||
/* istanbul ignore next */ | ||
if (neg) { | ||
num = num - 10; | ||
} | ||
/* istanbul ignore next */ | ||
if (magnitude !== 0) { | ||
// parseFloat is more reliable than pow due to rounding errors | ||
// e.g. Number.MAX_VALUE would return Infinity if we did | ||
// num * Math.pow(10, magnitude); | ||
num = parseFloat(num + 'e' + magnitude); | ||
} | ||
} | ||
return {num: num, length : i - originalIdx}; | ||
} | ||
// move up the stack while parsing | ||
// this function moved outside of parseIndexableString for performance | ||
function pop(stack, metaStack) { | ||
var obj = stack.pop(); | ||
if (metaStack.length) { | ||
var lastMetaElement = metaStack[metaStack.length - 1]; | ||
if (obj === lastMetaElement.element) { | ||
// popping a meta-element, e.g. an object whose value is another object | ||
metaStack.pop(); | ||
lastMetaElement = metaStack[metaStack.length - 1]; | ||
} | ||
var element = lastMetaElement.element; | ||
var lastElementIndex = lastMetaElement.index; | ||
if (Array.isArray(element)) { | ||
element.push(obj); | ||
} else if (lastElementIndex === stack.length - 2) { // obj with key+value | ||
var key = stack.pop(); | ||
element[key] = obj; | ||
} else { | ||
stack.push(obj); // obj with key only | ||
} | ||
} | ||
} | ||
function parseIndexableString(str) { | ||
var stack = []; | ||
var metaStack = []; // stack for arrays and objects | ||
var i = 0; | ||
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ | ||
while (true) { | ||
var collationIndex = str[i++]; | ||
if (collationIndex === '\u0000') { | ||
if (stack.length === 1) { | ||
return stack.pop(); | ||
} else { | ||
pop(stack, metaStack); | ||
continue; | ||
} | ||
} | ||
switch (collationIndex) { | ||
case '1': | ||
stack.push(null); | ||
break; | ||
case '2': | ||
stack.push(str[i] === '1'); | ||
i++; | ||
break; | ||
case '3': | ||
var parsedNum = parseNumber(str, i); | ||
stack.push(parsedNum.num); | ||
i += parsedNum.length; | ||
break; | ||
case '4': | ||
var parsedStr = ''; | ||
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ | ||
while (true) { | ||
var ch = str[i]; | ||
if (ch === '\u0000') { | ||
break; | ||
} | ||
parsedStr += ch; | ||
i++; | ||
} | ||
// perform the reverse of the order-preserving replacement | ||
// algorithm (see above) | ||
parsedStr = parsedStr.replace(/\u0001\u0001/g, '\u0000') | ||
.replace(/\u0001\u0002/g, '\u0001') | ||
.replace(/\u0002\u0002/g, '\u0002'); | ||
stack.push(parsedStr); | ||
break; | ||
case '5': | ||
var arrayElement = { element: [], index: stack.length }; | ||
stack.push(arrayElement.element); | ||
metaStack.push(arrayElement); | ||
break; | ||
case '6': | ||
var objElement = { element: {}, index: stack.length }; | ||
stack.push(objElement.element); | ||
metaStack.push(objElement); | ||
break; | ||
/* istanbul ignore next */ | ||
default: | ||
throw new Error( | ||
'bad collationIndex or unexpectedly reached end of input: ' + | ||
collationIndex); | ||
} | ||
} | ||
} | ||
function arrayCollate(a, b) { | ||
var len = Math.min(a.length, b.length); | ||
for (var i = 0; i < len; i++) { | ||
var sort = collate(a[i], b[i]); | ||
if (sort !== 0) { | ||
return sort; | ||
} | ||
} | ||
return (a.length === b.length) ? 0 : | ||
(a.length > b.length) ? 1 : -1; | ||
} | ||
function stringCollate(a, b) { | ||
// See: https://github.com/daleharvey/pouchdb/issues/40 | ||
// This is incompatible with the CouchDB implementation, but its the | ||
// best we can do for now | ||
return (a === b) ? 0 : ((a > b) ? 1 : -1); | ||
} | ||
function objectCollate(a, b) { | ||
var ak = Object.keys(a), bk = Object.keys(b); | ||
var len = Math.min(ak.length, bk.length); | ||
for (var i = 0; i < len; i++) { | ||
// First sort the keys | ||
var sort = collate(ak[i], bk[i]); | ||
if (sort !== 0) { | ||
return sort; | ||
} | ||
// if the keys are equal sort the values | ||
sort = collate(a[ak[i]], b[bk[i]]); | ||
if (sort !== 0) { | ||
return sort; | ||
} | ||
} | ||
return (ak.length === bk.length) ? 0 : | ||
(ak.length > bk.length) ? 1 : -1; | ||
} | ||
// The collation is defined by erlangs ordered terms | ||
// the atoms null, true, false come first, then numbers, strings, | ||
// arrays, then objects | ||
// null/undefined/NaN/Infinity/-Infinity are all considered null | ||
function collationIndex(x) { | ||
var id = ['boolean', 'number', 'string', 'object']; | ||
var idx = id.indexOf(typeof x); | ||
//false if -1 otherwise true, but fast!!!!1 | ||
if (~idx) { | ||
if (x === null) { | ||
return 1; | ||
} | ||
if (Array.isArray(x)) { | ||
return 5; | ||
} | ||
return idx < 3 ? (idx + 2) : (idx + 3); | ||
} | ||
/* istanbul ignore next */ | ||
if (Array.isArray(x)) { | ||
return 5; | ||
} | ||
} | ||
// conversion: | ||
// x yyy zz...zz | ||
// x = 0 for negative, 1 for 0, 2 for positive | ||
// y = exponent (for negative numbers negated) moved so that it's >= 0 | ||
// z = mantisse | ||
function numToIndexableString(num) { | ||
if (num === 0) { | ||
return '1'; | ||
} | ||
// convert number to exponential format for easier and | ||
// more succinct string sorting | ||
var expFormat = num.toExponential().split(/e\+?/); | ||
var magnitude = parseInt(expFormat[1], 10); | ||
var neg = num < 0; | ||
var result = neg ? '0' : '2'; | ||
// first sort by magnitude | ||
// it's easier if all magnitudes are positive | ||
var magForComparison = ((neg ? -magnitude : magnitude) - MIN_MAGNITUDE); | ||
var magString = padLeft((magForComparison).toString(), '0', MAGNITUDE_DIGITS); | ||
result += SEP + magString; | ||
// then sort by the factor | ||
var factor = Math.abs(parseFloat(expFormat[0])); // [1..10) | ||
/* istanbul ignore next */ | ||
if (neg) { // for negative reverse ordering | ||
factor = 10 - factor; | ||
} | ||
var factorStr = factor.toFixed(20); | ||
// strip zeros from the end | ||
factorStr = factorStr.replace(/\.?0+$/, ''); | ||
result += SEP + factorStr; | ||
return result; | ||
} | ||
/* | ||
* Simple task queue to sequentialize actions. Assumes | ||
@@ -21,3 +811,3 @@ * callbacks will eventually fire (once). | ||
function TaskQueue() { | ||
this.promise = new Promise(function (fulfill) {fulfill(); }); | ||
this.promise = new PouchPromise(function (fulfill) {fulfill(); }); | ||
} | ||
@@ -36,2 +826,6 @@ TaskQueue.prototype.add = function (promiseFactory) { | ||
function stringMd5(string) { | ||
return crypto.createHash('md5').update(string, 'binary').digest('hex'); | ||
} | ||
function createView(opts) { | ||
@@ -60,3 +854,3 @@ var sourceDB = opts.db; | ||
var depDbName = info.db_name + '-mrview-' + | ||
(temporary ? 'temp' : pouchdbMd5.stringMd5(viewSignature)); | ||
(temporary ? 'temp' : stringMd5(viewSignature)); | ||
@@ -79,3 +873,3 @@ // save the view name in the source db so it can be cleaned up if necessary | ||
} | ||
return pouchdbUtils.upsert(sourceDB, '_local/mrviews', diffFunction).then(function () { | ||
return upsert(sourceDB, '_local/mrviews', diffFunction).then(function () { | ||
return sourceDB.registerDependentDatabase(depDbName).then(function (res) { | ||
@@ -129,2 +923,69 @@ var db = res.db; | ||
var promisedCallback = function (promise, callback) { | ||
if (callback) { | ||
promise.then(function (res) { | ||
process.nextTick(function () { | ||
callback(null, res); | ||
}); | ||
}, function (reason) { | ||
process.nextTick(function () { | ||
callback(reason); | ||
}); | ||
}); | ||
} | ||
return promise; | ||
}; | ||
var callbackify = function (fun) { | ||
return getArguments(function (args) { | ||
var cb = args.pop(); | ||
var promise = fun.apply(this, args); | ||
if (typeof cb === 'function') { | ||
promisedCallback(promise, cb); | ||
} | ||
return promise; | ||
}); | ||
}; | ||
// Promise finally util similar to Q.finally | ||
var fin = function (promise, finalPromiseFactory) { | ||
return promise.then(function (res) { | ||
return finalPromiseFactory().then(function () { | ||
return res; | ||
}); | ||
}, function (reason) { | ||
return finalPromiseFactory().then(function () { | ||
throw reason; | ||
}); | ||
}); | ||
}; | ||
var sequentialize = function (queue, promiseFactory) { | ||
return function () { | ||
var args = arguments; | ||
var that = this; | ||
return queue.add(function () { | ||
return promiseFactory.apply(that, args); | ||
}); | ||
}; | ||
}; | ||
// uniq an array of strings, order not guaranteed | ||
// similar to underscore/lodash _.uniq | ||
var uniq = function (arr) { | ||
var map = {}; | ||
for (var i = 0, len = arr.length; i < len; i++) { | ||
map['$' + arr[i]] = true; | ||
} | ||
var keys = Object.keys(map); | ||
var output = new Array(keys.length); | ||
for (i = 0, len = keys.length; i < len; i++) { | ||
output[i] = keys[i].substring(1); | ||
} | ||
return output; | ||
}; | ||
var persistentQueues = {}; | ||
@@ -134,3 +995,3 @@ var tempViewQueue = new TaskQueue(); | ||
var log = pouchdbUtils.guardedConsole.bind(null, 'log'); | ||
var log = guardedConsole.bind(null, 'log'); | ||
@@ -153,3 +1014,3 @@ function parseViewName(name) { | ||
} catch (err) { | ||
pouchdbUtils.guardedConsole('error', | ||
guardedConsole('error', | ||
'The user\'s map/reduce function threw an uncaught error.\n' + | ||
@@ -159,3 +1020,3 @@ 'You can debug this error by doing:\n' + | ||
'Please double-check your map/reduce function.'); | ||
pouchdbUtils.guardedConsole('error', e); | ||
guardedConsole('error', e); | ||
} | ||
@@ -178,4 +1039,4 @@ } | ||
function sortByKeyThenValue(x, y) { | ||
var keyCompare = pouchdbCollate.collate(x.key, y.key); | ||
return keyCompare !== 0 ? keyCompare : pouchdbCollate.collate(x.value, y.value); | ||
var keyCompare = collate(x.key, y.key); | ||
return keyCompare !== 0 ? keyCompare : collate(x.value, y.value); | ||
} | ||
@@ -209,3 +1070,3 @@ | ||
var att = atts[filename]; | ||
atts[filename].data = pouchdbBinaryUtils.base64StringToBlobOrBuffer(att.data, att.content_type); | ||
atts[filename].data = b64ToBluffer(att.data, att.content_type); | ||
}); | ||
@@ -340,3 +1201,3 @@ }); | ||
typeof options[endkeyName] !== 'undefined' && | ||
pouchdbCollate.collate(options[startkeyName], options[endkeyName]) > 0) { | ||
collate(options[startkeyName], options[endkeyName]) > 0) { | ||
throw new QueryParseError('No rows can match your key range, ' + | ||
@@ -445,3 +1306,3 @@ 'reverse your start_key and end_key or set {descending : true}'); | ||
function customQuery(db, fun, opts) { | ||
return new Promise(function (resolve, reject) { | ||
return new PouchPromise(function (resolve, reject) { | ||
db._query(fun, opts, function (err, res) { | ||
@@ -460,3 +1321,3 @@ if (err) { | ||
function customViewCleanup(db) { | ||
return new Promise(function (resolve, reject) { | ||
return new PouchPromise(function (resolve, reject) { | ||
db._viewCleanup(function (err, res) { | ||
@@ -496,3 +1357,3 @@ if (err) { | ||
// for performance reasons (avoids unnecessary GETs) | ||
return Promise.resolve(defaultMetaDoc); | ||
return PouchPromise.resolve(defaultMetaDoc); | ||
} | ||
@@ -505,3 +1366,3 @@ return view.db.get(metaDocId).catch(defaultsTo(defaultMetaDoc)); | ||
// no keys, no need for a lookup | ||
return Promise.resolve({rows: []}); | ||
return PouchPromise.resolve({rows: []}); | ||
} | ||
@@ -549,3 +1410,3 @@ return view.db.allDocs({ | ||
}); | ||
metaDoc.keys = pouchdbMapreduceUtils.uniq(newKeys.concat(metaDoc.keys)); | ||
metaDoc.keys = uniq(newKeys.concat(metaDoc.keys)); | ||
kvDocs.push(metaDoc); | ||
@@ -571,6 +1432,6 @@ | ||
var docIds = Object.keys(docIdsToChangesAndEmits); | ||
return Promise.all(docIds.map(function (docId) { | ||
return PouchPromise.all(docIds.map(function (docId) { | ||
return getDocsToPersist(docId, view, docIdsToChangesAndEmits); | ||
})).then(function (listOfDocsToPersist) { | ||
var docsToPersist = pouchdbUtils.flatten(listOfDocsToPersist); | ||
var docsToPersist = flatten(listOfDocsToPersist); | ||
lastSeqDoc.seq = seq; | ||
@@ -594,3 +1455,3 @@ docsToPersist.push(lastSeqDoc); | ||
function updateView(view) { | ||
return pouchdbMapreduceUtils.sequentialize(getQueue(view), function () { | ||
return sequentialize(getQueue(view), function () { | ||
return updateViewInQueue(view); | ||
@@ -606,7 +1467,7 @@ })(); | ||
function emit(key, value) { | ||
var output = {id: doc._id, key: pouchdbCollate.normalizeKey(key)}; | ||
var output = {id: doc._id, key: normalizeKey(key)}; | ||
// Don't explicitly store the value unless it's defined and non-null. | ||
// This saves on storage space, because often people don't use it. | ||
if (typeof value !== 'undefined' && value !== null) { | ||
output.value = pouchdbCollate.normalizeKey(value); | ||
output.value = normalizeKey(value); | ||
} | ||
@@ -639,3 +1500,3 @@ mapResults.push(output); | ||
return new Promise(function (resolve, reject) { | ||
return new PouchPromise(function (resolve, reject) { | ||
@@ -678,6 +1539,6 @@ function complete() { | ||
var complexKey = [obj.key, obj.id]; | ||
if (pouchdbCollate.collate(obj.key, lastKey) === 0) { | ||
if (collate(obj.key, lastKey) === 0) { | ||
complexKey.push(j); // dup key+id, so make it unique | ||
} | ||
var indexableKey = pouchdbCollate.toIndexableString(complexKey); | ||
var indexableKey = toIndexableString(complexKey); | ||
indexableKeysToKeyValues[indexableKey] = obj; | ||
@@ -736,3 +1597,3 @@ lastKey = obj.key; | ||
if (last && pouchdbCollate.collate(last.groupKey, groupKey) === 0) { | ||
if (last && collate(last.groupKey, groupKey) === 0) { | ||
last.keys.push([e.key, e.id]); | ||
@@ -768,3 +1629,3 @@ last.values.push(e.value); | ||
function queryView(view, opts) { | ||
return pouchdbMapreduceUtils.sequentialize(getQueue(view), function () { | ||
return sequentialize(getQueue(view), function () { | ||
return queryViewInQueue(view, opts); | ||
@@ -805,3 +1666,3 @@ })(); | ||
var parsedKeyAndDocId = pouchdbCollate.parseIndexableString(result.doc._id); | ||
var parsedKeyAndDocId = parseIndexableString(result.doc._id); | ||
return { | ||
@@ -828,3 +1689,3 @@ key: parsedKeyAndDocId[0], | ||
if (opts.include_docs) { | ||
var docIds = pouchdbMapreduceUtils.uniq(rows.map(rowToDocId)); | ||
var docIds = uniq(rows.map(rowToDocId)); | ||
@@ -862,8 +1723,8 @@ return view.sourceDB.allDocs({ | ||
var viewOpts = { | ||
startkey : pouchdbCollate.toIndexableString([key]), | ||
endkey : pouchdbCollate.toIndexableString([key, {}]) | ||
startkey : toIndexableString([key]), | ||
endkey : toIndexableString([key, {}]) | ||
}; | ||
return fetchFromView(viewOpts); | ||
}); | ||
return Promise.all(fetchPromises).then(pouchdbUtils.flatten).then(onMapResultsReady); | ||
return PouchPromise.all(fetchPromises).then(flatten).then(onMapResultsReady); | ||
} else { // normal query, no 'keys' | ||
@@ -881,4 +1742,4 @@ var viewOpts = { | ||
viewOpts.startkey = opts.descending ? | ||
pouchdbCollate.toIndexableString([opts.startkey, {}]) : | ||
pouchdbCollate.toIndexableString([opts.startkey]); | ||
toIndexableString([opts.startkey, {}]) : | ||
toIndexableString([opts.startkey]); | ||
} | ||
@@ -891,8 +1752,8 @@ if (typeof opts.endkey !== 'undefined') { | ||
viewOpts.endkey = pouchdbCollate.toIndexableString( | ||
viewOpts.endkey = toIndexableString( | ||
inclusiveEnd ? [opts.endkey, {}] : [opts.endkey]); | ||
} | ||
if (typeof opts.key !== 'undefined') { | ||
var keyStart = pouchdbCollate.toIndexableString([opts.key]); | ||
var keyEnd = pouchdbCollate.toIndexableString([opts.key, {}]); | ||
var keyStart = toIndexableString([opts.key]); | ||
var keyEnd = toIndexableString([opts.key, {}]); | ||
if (viewOpts.descending) { | ||
@@ -962,7 +1823,7 @@ viewOpts.endkey = keyStart; | ||
var destroyPromises = dbsToDelete.map(function (viewDBName) { | ||
return pouchdbMapreduceUtils.sequentialize(getQueue(viewDBName), function () { | ||
return sequentialize(getQueue(viewDBName), function () { | ||
return new db.constructor(viewDBName, db.__opts).destroy(); | ||
})(); | ||
}); | ||
return Promise.all(destroyPromises).then(function () { | ||
return PouchPromise.all(destroyPromises).then(function () { | ||
return {ok: true}; | ||
@@ -974,3 +1835,3 @@ }); | ||
var viewCleanup = pouchdbMapreduceUtils.callbackify(function () { | ||
var viewCleanup = callbackify(function () { | ||
var db = this; | ||
@@ -1013,3 +1874,3 @@ if (db.type() === 'http') { | ||
} | ||
return pouchdbMapreduceUtils.fin(updateView(view).then(function () { | ||
return fin(updateView(view).then(function () { | ||
return queryView(view, opts); | ||
@@ -1071,6 +1932,6 @@ }), cleanup); | ||
var db = this; | ||
var promise = Promise.resolve().then(function () { | ||
var promise = PouchPromise.resolve().then(function () { | ||
return queryPromised(db, fun, opts); | ||
}); | ||
pouchdbMapreduceUtils.promisedCallback(promise, callback); | ||
promisedCallback(promise, callback); | ||
return promise; | ||
@@ -1077,0 +1938,0 @@ }; |
{ | ||
"name": "pouchdb-mapreduce", | ||
"version": "6.0.2", | ||
"version": "6.0.3", | ||
"description": "PouchDB's map/reduce query API as a plugin.", | ||
@@ -17,9 +17,9 @@ "main": "./lib/index.js", | ||
"inherits": "2.0.1", | ||
"pouchdb-binary-utils": "6.0.2", | ||
"pouchdb-collate": "6.0.2", | ||
"pouchdb-errors": "6.0.2", | ||
"pouchdb-mapreduce-utils": "6.0.2", | ||
"pouchdb-md5": "6.0.2", | ||
"pouchdb-promise": "6.0.2", | ||
"pouchdb-utils": "6.0.2", | ||
"pouchdb-binary-utils": "6.0.3", | ||
"pouchdb-collate": "6.0.3", | ||
"pouchdb-errors": "6.0.3", | ||
"pouchdb-mapreduce-utils": "6.0.3", | ||
"pouchdb-md5": "6.0.3", | ||
"pouchdb-promise": "6.0.3", | ||
"pouchdb-utils": "6.0.3", | ||
"scope-eval": "0.0.3", | ||
@@ -32,4 +32,4 @@ "spark-md5": "2.0.2" | ||
"peerDependencies": { | ||
"pouchdb-core": "6.0.2" | ||
"pouchdb-core": "6.0.3" | ||
} | ||
} |
152973
39.18%4552
53.06%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated
Updated
Updated