Created
March 25, 2014 19:34
-
-
Save tlvince/9769464 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.PouchDB=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ | |
"use strict"; | |
var utils = _dereq_('./utils'); | |
var merge = _dereq_('./merge'); | |
var errors = _dereq_('./deps/errors'); | |
var EventEmitter = _dereq_('events').EventEmitter; | |
/* | |
* A generic pouch adapter | |
*/ | |
// returns first element of arr satisfying callback predicate | |
function arrayFirst(arr, callback) { | |
for (var i = 0; i < arr.length; i++) { | |
if (callback(arr[i], i) === true) { | |
return arr[i]; | |
} | |
} | |
return false; | |
} | |
// Wrapper for functions that call the bulkdocs api with a single doc, | |
// if the first result is an error, return an error | |
function yankError(callback) { | |
return function (err, results) { | |
if (err || results[0].error) { | |
callback(err || results[0]); | |
} else { | |
callback(null, results[0]); | |
} | |
}; | |
} | |
// for every node in a revision tree computes its distance from the closest | |
// leaf | |
function computeHeight(revs) { | |
var height = {}; | |
var edges = []; | |
merge.traverseRevTree(revs, function (isLeaf, pos, id, prnt) { | |
var rev = pos + "-" + id; | |
if (isLeaf) { | |
height[rev] = 0; | |
} | |
if (prnt !== undefined) { | |
edges.push({from: prnt, to: rev}); | |
} | |
return rev; | |
}); | |
edges.reverse(); | |
edges.forEach(function (edge) { | |
if (height[edge.from] === undefined) { | |
height[edge.from] = 1 + height[edge.to]; | |
} else { | |
height[edge.from] = Math.min(height[edge.from], 1 + height[edge.to]); | |
} | |
}); | |
return height; | |
} | |
utils.inherits(AbstractPouchDB, EventEmitter); | |
module.exports = AbstractPouchDB; | |
function AbstractPouchDB() { | |
var self = this; | |
EventEmitter.call(this); | |
self.autoCompact = function (callback) { | |
if (!self.auto_compaction) { | |
return callback; | |
} | |
return function (err, res) { | |
if (err) { | |
callback(err); | |
} else { | |
var count = res.length; | |
var decCount = function () { | |
count--; | |
if (!count) { | |
callback(null, res); | |
} | |
}; | |
res.forEach(function (doc) { | |
if (doc.ok) { | |
// TODO: we need better error handling | |
self.compactDocument(doc.id, 1, decCount); | |
} else { | |
decCount(); | |
} | |
}); | |
} | |
}; | |
}; | |
var listeners = 0, changes; | |
var eventNames = ['change', 'delete', 'create', 'update']; | |
this.on('newListener', function (eventName) { | |
if (~eventNames.indexOf(eventName)) { | |
if (listeners) { | |
listeners++; | |
return; | |
} else { | |
listeners++; | |
} | |
} else { | |
return; | |
} | |
var lastChange = 0; | |
changes = this.changes({ | |
conflicts: true, | |
include_docs: true, | |
continuous: true, | |
since: 'latest', | |
onChange: function (change) { | |
if (change.seq <= lastChange) { | |
return; | |
} | |
lastChange = change.seq; | |
self.emit('change', change); | |
if (change.doc._deleted) { | |
self.emit('delete', change); | |
} else if (change.doc._rev.split('-')[0] === '1') { | |
self.emit('create', change); | |
} else { | |
self.emit('update', change); | |
} | |
} | |
}); | |
}); | |
this.on('removeListener', function (eventName) { | |
if (~eventNames.indexOf(eventName)) { | |
listeners--; | |
if (listeners) { | |
return; | |
} | |
} else { | |
return; | |
} | |
changes.cancel(); | |
}); | |
} | |
AbstractPouchDB.prototype.post = utils.adapterFun('post', function (doc, opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
if (typeof doc !== 'object' || Array.isArray(doc)) { | |
return callback(errors.NOT_AN_OBJECT); | |
} | |
return this.bulkDocs({docs: [doc]}, opts, | |
this.autoCompact(yankError(callback))); | |
}); | |
AbstractPouchDB.prototype.put = utils.adapterFun('put', utils.getArguments(function (args) { | |
var temp, temptype, opts, callback; | |
var doc = args.shift(); | |
var id = '_id' in doc; | |
if (typeof doc !== 'object' || Array.isArray(doc)) { | |
callback = args.pop(); | |
return callback(errors.NOT_AN_OBJECT); | |
} | |
doc = utils.extend(true, {}, doc); | |
while (true) { | |
temp = args.shift(); | |
temptype = typeof temp; | |
if (temptype === "string" && !id) { | |
doc._id = temp; | |
id = true; | |
} else if (temptype === "string" && id && !('_rev' in doc)) { | |
doc._rev = temp; | |
} else if (temptype === "object") { | |
opts = temp; | |
} else if (temptype === "function") { | |
callback = temp; | |
} | |
if (!args.length) { | |
break; | |
} | |
} | |
opts = opts || {}; | |
var error = utils.invalidIdError(doc._id); | |
if (error) { | |
return callback(error); | |
} | |
return this.bulkDocs({docs: [doc]}, opts, | |
this.autoCompact(yankError(callback))); | |
})); | |
AbstractPouchDB.prototype.putAttachment = utils.adapterFun('putAttachment', function (docId, attachmentId, rev, blob, type, callback) { | |
var api = this; | |
if (typeof type === 'function') { | |
callback = type; | |
type = blob; | |
blob = rev; | |
rev = null; | |
} | |
if (typeof type === 'undefined') { | |
type = blob; | |
blob = rev; | |
rev = null; | |
} | |
function createAttachment(doc) { | |
doc._attachments = doc._attachments || {}; | |
doc._attachments[attachmentId] = { | |
content_type: type, | |
data: blob | |
}; | |
api.put(doc, callback); | |
} | |
api.get(docId, function (err, doc) { | |
// create new doc | |
if (err && err.error === errors.MISSING_DOC.error) { | |
createAttachment({_id: docId}); | |
return; | |
} | |
if (err) { | |
callback(err); | |
return; | |
} | |
if (doc._rev !== rev) { | |
callback(errors.REV_CONFLICT); | |
return; | |
} | |
createAttachment(doc); | |
}); | |
}); | |
AbstractPouchDB.prototype.removeAttachment = utils.adapterFun('removeAttachment', function (docId, attachmentId, rev, callback) { | |
var self = this; | |
self.get(docId, function (err, obj) { | |
if (err) { | |
callback(err); | |
return; | |
} | |
if (obj._rev !== rev) { | |
callback(errors.REV_CONFLICT); | |
return; | |
} | |
if (!obj._attachments) { | |
return callback(); | |
} | |
delete obj._attachments[attachmentId]; | |
if (Object.keys(obj._attachments).length === 0) { | |
delete obj._attachments; | |
} | |
self.put(obj, callback); | |
}); | |
}); | |
AbstractPouchDB.prototype.remove = utils.adapterFun('remove', function (doc, opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
if (opts === undefined) { | |
opts = {}; | |
} | |
opts = utils.extend(true, {}, opts); | |
opts.was_delete = true; | |
var newDoc = {_id: doc._id, _rev: doc._rev}; | |
newDoc._deleted = true; | |
return this.bulkDocs({docs: [newDoc]}, opts, yankError(callback)); | |
}); | |
AbstractPouchDB.prototype.revsDiff = utils.adapterFun('revsDiff', function (req, opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
opts = utils.extend(true, {}, opts); | |
var ids = Object.keys(req); | |
var count = 0; | |
var missing = {}; | |
function addToMissing(id, revId) { | |
if (!missing[id]) { | |
missing[id] = {missing: []}; | |
} | |
missing[id].missing.push(revId); | |
} | |
function processDoc(id, rev_tree) { | |
// Is this fast enough? Maybe we should switch to a set simulated by a map | |
var missingForId = req[id].slice(0); | |
merge.traverseRevTree(rev_tree, function (isLeaf, pos, revHash, ctx, | |
opts) { | |
var rev = pos + '-' + revHash; | |
var idx = missingForId.indexOf(rev); | |
if (idx === -1) { | |
return; | |
} | |
missingForId.splice(idx, 1); | |
if (opts.status !== 'available') { | |
addToMissing(id, rev); | |
} | |
}); | |
// Traversing the tree is synchronous, so now `missingForId` contains | |
// revisions that were not found in the tree | |
missingForId.forEach(function (rev) { | |
addToMissing(id, rev); | |
}); | |
} | |
ids.map(function (id) { | |
this._getRevisionTree(id, function (err, rev_tree) { | |
if (err && err.name === 'not_found' && err.message === 'missing') { | |
missing[id] = {missing: req[id]}; | |
} else if (err) { | |
return callback(err); | |
} else { | |
processDoc(id, rev_tree); | |
} | |
if (++count === ids.length) { | |
return callback(null, missing); | |
} | |
}); | |
}, this); | |
}); | |
// compact one document and fire callback | |
// by compacting we mean removing all revisions which | |
// are further from the leaf in revision tree than max_height | |
AbstractPouchDB.prototype.compactDocument = function (docId, max_height, callback) { | |
var self = this; | |
this._getRevisionTree(docId, function (err, rev_tree) { | |
if (err) { | |
return callback(err); | |
} | |
var height = computeHeight(rev_tree); | |
var candidates = []; | |
var revs = []; | |
Object.keys(height).forEach(function (rev) { | |
if (height[rev] > max_height) { | |
candidates.push(rev); | |
} | |
}); | |
merge.traverseRevTree(rev_tree, function (isLeaf, pos, revHash, ctx, opts) { | |
var rev = pos + '-' + revHash; | |
if (opts.status === 'available' && candidates.indexOf(rev) !== -1) { | |
opts.status = 'missing'; | |
revs.push(rev); | |
} | |
}); | |
self._doCompaction(docId, rev_tree, revs, callback); | |
}); | |
}; | |
// compact the whole database using single document | |
// compaction | |
AbstractPouchDB.prototype.compact = utils.adapterFun('compact', function (opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
var self = this; | |
this.changes({complete: function (err, res) { | |
if (err) { | |
callback(); // TODO: silently fail | |
return; | |
} | |
var count = res.results.length; | |
if (!count) { | |
callback(); | |
return; | |
} | |
res.results.forEach(function (row) { | |
self.compactDocument(row.id, 0, function () { | |
count--; | |
if (!count) { | |
callback(); | |
} | |
}); | |
}); | |
}}); | |
}); | |
/* Begin api wrappers. Specific functionality to storage belongs in the _[method] */ | |
AbstractPouchDB.prototype.get = utils.adapterFun('get', function (id, opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
if (typeof id !== 'string') { | |
return callback(errors.INVALID_ID); | |
} | |
var leaves = [], self = this; | |
function finishOpenRevs() { | |
var result = []; | |
var count = leaves.length; | |
if (!count) { | |
return callback(null, result); | |
} | |
// order with open_revs is unspecified | |
leaves.forEach(function (leaf) { | |
self.get(id, {rev: leaf, revs: opts.revs, attachments: opts.attachments}, function (err, doc) { | |
if (!err) { | |
result.push({ok: doc}); | |
} else { | |
result.push({missing: leaf}); | |
} | |
count--; | |
if (!count) { | |
callback(null, result); | |
} | |
}); | |
}); | |
} | |
if (opts.open_revs) { | |
if (opts.open_revs === "all") { | |
this._getRevisionTree(id, function (err, rev_tree) { | |
if (err) { | |
// if there's no such document we should treat this | |
// situation the same way as if revision tree was empty | |
rev_tree = []; | |
} | |
leaves = merge.collectLeaves(rev_tree).map(function (leaf) { | |
return leaf.rev; | |
}); | |
finishOpenRevs(); | |
}); | |
} else { | |
if (Array.isArray(opts.open_revs)) { | |
leaves = opts.open_revs; | |
for (var i = 0; i < leaves.length; i++) { | |
var l = leaves[i]; | |
// looks like it's the only thing couchdb checks | |
if (!(typeof(l) === "string" && /^\d+-/.test(l))) { | |
return callback(errors.error(errors.BAD_REQUEST, | |
"Invalid rev format")); | |
} | |
} | |
finishOpenRevs(); | |
} else { | |
return callback(errors.error(errors.UNKNOWN_ERROR, | |
'function_clause')); | |
} | |
} | |
return; // open_revs does not like other options | |
} | |
return this._get(id, opts, function (err, result) { | |
opts = utils.extend(true, {}, opts); | |
if (err) { | |
return callback(err); | |
} | |
var doc = result.doc; | |
var metadata = result.metadata; | |
var ctx = result.ctx; | |
if (opts.conflicts) { | |
var conflicts = merge.collectConflicts(metadata); | |
if (conflicts.length) { | |
doc._conflicts = conflicts; | |
} | |
} | |
if (opts.revs || opts.revs_info) { | |
var paths = merge.rootToLeaf(metadata.rev_tree); | |
var path = arrayFirst(paths, function (arr) { | |
return arr.ids.map(function (x) { return x.id; }) | |
.indexOf(doc._rev.split('-')[1]) !== -1; | |
}); | |
path.ids.splice(path.ids.map(function (x) {return x.id; }) | |
.indexOf(doc._rev.split('-')[1]) + 1); | |
path.ids.reverse(); | |
if (opts.revs) { | |
doc._revisions = { | |
start: (path.pos + path.ids.length) - 1, | |
ids: path.ids.map(function (rev) { | |
return rev.id; | |
}) | |
}; | |
} | |
if (opts.revs_info) { | |
var pos = path.pos + path.ids.length; | |
doc._revs_info = path.ids.map(function (rev) { | |
pos--; | |
return { | |
rev: pos + '-' + rev.id, | |
status: rev.opts.status | |
}; | |
}); | |
} | |
} | |
if (opts.local_seq) { | |
doc._local_seq = result.metadata.seq; | |
} | |
if (opts.attachments && doc._attachments) { | |
var attachments = doc._attachments; | |
var count = Object.keys(attachments).length; | |
if (count === 0) { | |
return callback(null, doc); | |
} | |
Object.keys(attachments).forEach(function (key) { | |
this._getAttachment(attachments[key], {encode: true, ctx: ctx}, function (err, data) { | |
doc._attachments[key].data = data; | |
if (!--count) { | |
callback(null, doc); | |
} | |
}); | |
}, self); | |
} else { | |
if (doc._attachments) { | |
for (var key in doc._attachments) { | |
if (doc._attachments.hasOwnProperty(key)) { | |
doc._attachments[key].stub = true; | |
} | |
} | |
} | |
callback(null, doc); | |
} | |
}); | |
}); | |
AbstractPouchDB.prototype.getAttachment = utils.adapterFun('getAttachment', function (docId, attachmentId, opts, callback) { | |
var self = this; | |
if (opts instanceof Function) { | |
callback = opts; | |
opts = {}; | |
} | |
opts = utils.extend(true, {}, opts); | |
this._get(docId, opts, function (err, res) { | |
if (err) { | |
return callback(err); | |
} | |
if (res.doc._attachments && res.doc._attachments[attachmentId]) { | |
opts.ctx = res.ctx; | |
self._getAttachment(res.doc._attachments[attachmentId], opts, callback); | |
} else { | |
return callback(errors.MISSING_DOC); | |
} | |
}); | |
}); | |
AbstractPouchDB.prototype.allDocs = utils.adapterFun('allDocs', function (opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
opts = utils.extend(true, {}, opts); | |
if ('keys' in opts) { | |
var incompatibleOpt = ['startkey', 'endkey', 'key'].filter(function (incompatibleOpt) { | |
return incompatibleOpt in opts; | |
})[0]; | |
if (incompatibleOpt) { | |
callback(errors.error(errors.QUERY_PARSE_ERROR, | |
'Query parameter `' + incompatibleOpt + '` is not compatible with multi-get' | |
)); | |
return; | |
} | |
} | |
if (typeof opts.skip === 'undefined') { | |
opts.skip = 0; | |
} | |
return this._allDocs(opts, callback); | |
}); | |
function processChange(doc, metadata, opts) { | |
var changeList = [{rev: doc._rev}]; | |
if (opts.style === 'all_docs') { | |
changeList = merge.collectLeaves(metadata.rev_tree) | |
.map(function (x) { return {rev: x.rev}; }); | |
} | |
var change = { | |
id: metadata.id, | |
changes: changeList, | |
doc: doc | |
}; | |
if (utils.isDeleted(metadata, doc._rev)) { | |
change.deleted = true; | |
} | |
if (opts.conflicts) { | |
change.doc._conflicts = merge.collectConflicts(metadata); | |
if (!change.doc._conflicts.length) { | |
delete change.doc._conflicts; | |
} | |
} | |
return change; | |
} | |
function doChanges(api, opts, promise) { | |
var callback = opts.complete; | |
opts = utils.extend(true, {}, opts); | |
if ('live' in opts && !('continuous' in opts)) { | |
opts.continuous = opts.live; | |
} | |
opts.processChange = processChange; | |
if (!opts.since) { | |
opts.since = 0; | |
} | |
if (opts.since === 'latest') { | |
api.info(function (err, info) { | |
if (promise.isCancelled) { | |
callback(null, {status: 'cancelled'}); | |
return; | |
} | |
if (err) { | |
callback(err); | |
return; | |
} | |
opts.since = info.update_seq - 1; | |
doChanges(api, opts, promise, callback); | |
}); | |
return; | |
} | |
if (api.type() !== 'http' && opts.filter && typeof opts.filter === 'string') { | |
if (opts.filter === '_view') { | |
if (opts.view && typeof opts.view === 'string') { | |
// fetch a view from a design doc, make it behave like a filter | |
var viewName = opts.view.split('/'); | |
api.get('_design/' + viewName[0], function (err, ddoc) { | |
if (promise.isCancelled) { | |
callback(null, {status: 'cancelled'}); | |
return; | |
} | |
if (err) { | |
callback(err); | |
return; | |
} | |
if (ddoc && ddoc.views && ddoc.views[viewName[1]]) { | |
/*jshint evil: true */ | |
var filter = eval('(function () {' + | |
' return function (doc) {' + | |
' var emitted = false;' + | |
' var emit = function (a, b) {' + | |
' emitted = true;' + | |
' };' + | |
' var view = ' + ddoc.views[viewName[1]].map + ';' + | |
' view(doc);' + | |
' if (emitted) {' + | |
' return true;' + | |
' }' + | |
' }' + | |
'})()'); | |
opts.filter = filter; | |
doChanges(api, opts, promise, callback); | |
return; | |
} else { | |
var msg = ddoc.views ? 'missing json key: ' + viewName[1] : | |
'missing json key: views'; | |
err = err || errors.error(errors.MISSING_DOC, msg); | |
callback(err); | |
return; | |
} | |
}); | |
} else { | |
var err = errors.error(errors.BAD_REQUEST, | |
'`view` filter parameter is not provided.'); | |
callback(err); | |
return; | |
} | |
} else { | |
// fetch a filter from a design doc | |
var filterName = opts.filter.split('/'); | |
api.get('_design/' + filterName[0], function (err, ddoc) { | |
if (promise.isCancelled) { | |
callback(null, {status: 'cancelled'}); | |
return; | |
} | |
if (err) { | |
callback(err); | |
return; | |
} | |
if (ddoc && ddoc.filters && ddoc.filters[filterName[1]]) { | |
/*jshint evil: true */ | |
var filter = eval('(function () { return ' + | |
ddoc.filters[filterName[1]] + ' })()'); | |
opts.filter = filter; | |
doChanges(api, opts, promise, callback); | |
return; | |
} else { | |
var msg = (ddoc && ddoc.filters) ? 'missing json key: ' + filterName[1] | |
: 'missing json key: filters'; | |
err = err || errors.error(errors.MISSING_DOC, msg); | |
callback(err); | |
return; | |
} | |
}); | |
} | |
return; | |
} | |
if (!('descending' in opts)) { | |
opts.descending = false; | |
} | |
// 0 and 1 should return 1 document | |
opts.limit = opts.limit === 0 ? 1 : opts.limit; | |
opts.complete = callback; | |
var newPromise = api._changes(opts); | |
if (newPromise && typeof newPromise.cancel === 'function') { | |
var cancel = promise.cancel; | |
promise.cancel = utils.getArguments(function (args) { | |
newPromise.cancel(); | |
cancel.apply(this, args); | |
}); | |
} | |
} | |
AbstractPouchDB.prototype.changes = function (opts) { | |
return utils.cancellableFun(doChanges, this, opts); | |
}; | |
AbstractPouchDB.prototype.close = utils.adapterFun('close', function (callback) { | |
return this._close(callback); | |
}); | |
AbstractPouchDB.prototype.info = utils.adapterFun('info', function (callback) { | |
var self = this; | |
this._info(function (err, info) { | |
if (err) { | |
return callback(err); | |
} | |
var len = self.prefix.length; | |
if (info.db_name.length > len && info.db_name.slice(0, len) === self.prefix) { | |
info.db_name = info.db_name.slice(len); | |
} | |
callback(null, info); | |
}); | |
}); | |
AbstractPouchDB.prototype.id = utils.adapterFun('id', function (callback) { | |
return this._id(callback); | |
}); | |
AbstractPouchDB.prototype.type = function () { | |
return (typeof this._type === 'function') ? this._type() : this.adapter; | |
}; | |
AbstractPouchDB.prototype.bulkDocs = utils.adapterFun('bulkDocs', function (req, opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
if (!opts) { | |
opts = {}; | |
} else { | |
opts = utils.extend(true, {}, opts); | |
} | |
if (!req || !req.docs) { | |
return callback(errors.MISSING_BULK_DOCS); | |
} | |
if (!Array.isArray(req.docs)) { | |
return callback(errors.QUERY_PARSE_ERROR); | |
} | |
for (var i = 0; i < req.docs.length; ++i) { | |
if (typeof req.docs[i] !== 'object' || Array.isArray(req.docs[i])) { | |
return callback(errors.NOT_AN_OBJECT); | |
} | |
} | |
req = utils.extend(true, {}, req); | |
if (!('new_edits' in opts)) { | |
if ('new_edits' in req) { | |
opts.new_edits = req.new_edits; | |
} else { | |
opts.new_edits = true; | |
} | |
} | |
return this._bulkDocs(req, opts, this.autoCompact(callback)); | |
}); | |
},{"./deps/errors":8,"./merge":14,"./utils":18,"events":22}],2:[function(_dereq_,module,exports){ | |
"use strict"; | |
var utils = _dereq_('../utils'); | |
var errors = _dereq_('../deps/errors'); | |
// parseUri 1.2.2 | |
// (c) Steven Levithan <stevenlevithan.com> | |
// MIT License | |
function parseUri(str) { | |
var o = parseUri.options; | |
var m = o.parser[o.strictMode ? "strict" : "loose"].exec(str); | |
var uri = {}; | |
var i = 14; | |
while (i--) { | |
uri[o.key[i]] = m[i] || ""; | |
} | |
uri[o.q.name] = {}; | |
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { | |
if ($1) { | |
uri[o.q.name][$1] = $2; | |
} | |
}); | |
return uri; | |
} | |
function encodeDocId(id) { | |
if (/^_(design|local)/.test(id)) { | |
return id; | |
} | |
return encodeURIComponent(id); | |
} | |
parseUri.options = { | |
strictMode: false, | |
key: ["source", "protocol", "authority", "userInfo", "user", "password", "host", | |
"port", "relative", "path", "directory", "file", "query", "anchor"], | |
q: { | |
name: "queryKey", | |
parser: /(?:^|&)([^&=]*)=?([^&]*)/g | |
}, | |
parser: { | |
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, | |
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ | |
} | |
}; | |
// Get all the information you possibly can about the URI given by name and | |
// return it as a suitable object. | |
function getHost(name, opts) { | |
// If the given name contains "http:" | |
if (/http(s?):/.test(name)) { | |
// Prase the URI into all its little bits | |
var uri = parseUri(name); | |
// Store the fact that it is a remote URI | |
uri.remote = true; | |
// Store the user and password as a separate auth object | |
if (uri.user || uri.password) { | |
uri.auth = {username: uri.user, password: uri.password}; | |
} | |
// Split the path part of the URI into parts using '/' as the delimiter | |
// after removing any leading '/' and any trailing '/' | |
var parts = uri.path.replace(/(^\/|\/$)/g, '').split('/'); | |
// Store the first part as the database name and remove it from the parts | |
// array | |
uri.db = parts.pop(); | |
// Restore the path by joining all the remaining parts (all the parts | |
// except for the database name) with '/'s | |
uri.path = parts.join('/'); | |
opts = opts || {}; | |
opts = utils.extend(true, {}, opts); | |
uri.headers = opts.headers || {}; | |
if (opts.auth || uri.auth) { | |
var nAuth = opts.auth || uri.auth; | |
var token = utils.btoa(nAuth.username + ':' + nAuth.password); | |
uri.headers.Authorization = 'Basic ' + token; | |
} | |
if (opts.headers) { | |
uri.headers = opts.headers; | |
} | |
return uri; | |
} | |
// If the given name does not contain 'http:' then return a very basic object | |
// with no host, the current path, the given name as the database name and no | |
// username/password | |
return {host: '', path: '/', db: name, auth: false}; | |
} | |
// Generate a URL with the host data given by opts and the given path | |
function genDBUrl(opts, path) { | |
return genUrl(opts, opts.db + '/' + path); | |
} | |
// Generate a URL with the host data given by opts and the given path | |
function genUrl(opts, path) { | |
if (opts.remote) { | |
// If the host already has a path, then we need to have a path delimiter | |
// Otherwise, the path delimiter is the empty string | |
var pathDel = !opts.path ? '' : '/'; | |
// If the host already has a path, then we need to have a path delimiter | |
// Otherwise, the path delimiter is the empty string | |
return opts.protocol + '://' + opts.host + ':' + opts.port + '/' + opts.path + pathDel + path; | |
} | |
return '/' + path; | |
} | |
// Implements the PouchDB API for dealing with CouchDB instances over HTTP | |
function HttpPouch(opts, callback) { | |
// The functions that will be publicly available for HttpPouch | |
var api = this; | |
api.getHost = opts.getHost ? opts.getHost : getHost; | |
// Parse the URI given by opts.name into an easy-to-use object | |
var host = api.getHost(opts.name, opts); | |
// Generate the database URL based on the host | |
var db_url = genDBUrl(host, ''); | |
var ajaxOpts = opts.ajax || {}; | |
opts = utils.extend(true, {}, opts); | |
function ajax(options, callback) { | |
return utils.ajax(utils.extend({}, ajaxOpts, options), callback); | |
} | |
var uuids = { | |
list: [], | |
get: function (opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {count: 10}; | |
} | |
var cb = function (err, body) { | |
if (err || !('uuids' in body)) { | |
callback(err || errors.UNKNOWN_ERROR); | |
} else { | |
uuids.list = uuids.list.concat(body.uuids); | |
callback(null, "OK"); | |
} | |
}; | |
var params = '?count=' + opts.count; | |
ajax({ | |
headers: host.headers, | |
method: 'GET', | |
url: genUrl(host, '_uuids') + params | |
}, cb); | |
} | |
}; | |
// Create a new CouchDB database based on the given opts | |
var createDB = function () { | |
ajax({headers: host.headers, method: 'PUT', url: db_url}, function (err, ret) { | |
// If we get an "Unauthorized" error | |
if (err && err.status === 401) { | |
// Test if the database already exists | |
ajax({headers: host.headers, method: 'HEAD', url: db_url}, function (err, ret) { | |
// If there is still an error | |
if (err) { | |
// Give the error to the callback to deal with | |
callback(err); | |
} else { | |
// Continue as if there had been no errors | |
callback(null, api); | |
} | |
}); | |
// If there were no errros or if the only error is "Precondition Failed" | |
// (note: "Precondition Failed" occurs when we try to create a database | |
// that already exists) | |
} else if (!err || err.status === 412) { | |
// Continue as if there had been no errors | |
callback(null, api); | |
} else { | |
callback(err); | |
} | |
}); | |
}; | |
if (!opts.skipSetup) { | |
ajax({headers: host.headers, method: 'GET', url: db_url}, function (err, ret) { | |
//check if the db exists | |
if (err) { | |
if (err.status === 404) { | |
//if it doesn't, create it | |
createDB(); | |
} else { | |
callback(err); | |
} | |
} else { | |
//go do stuff with the db | |
callback(null, api); | |
} | |
}); | |
} | |
api.type = function () { | |
return 'http'; | |
}; | |
api.id = utils.adapterFun('id', function (callback) { | |
ajax({ | |
headers: host.headers, | |
method: 'GET', | |
url: genUrl(host, '') | |
}, function (err, result) { | |
if (err) { | |
callback(err); | |
} else { | |
var uuid = (result && result.uuid) ? | |
result.uuid + host.db : genDBUrl(host, ''); | |
callback(null, uuid); | |
} | |
}); | |
}); | |
api.request = utils.adapterFun('request', function (options, callback) { | |
options.headers = host.headers; | |
options.url = genDBUrl(host, options.url); | |
ajax(options, callback); | |
}); | |
// Sends a POST request to the host calling the couchdb _compact function | |
// version: The version of CouchDB it is running | |
api.compact = utils.adapterFun('compact', function (opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
opts = utils.extend(true, {}, opts); | |
ajax({ | |
headers: host.headers, | |
url: genDBUrl(host, '_compact'), | |
method: 'POST' | |
}, function () { | |
function ping() { | |
api.info(function (err, res) { | |
if (!res.compact_running) { | |
callback(); | |
} else { | |
setTimeout(ping, opts.interval || 200); | |
} | |
}); | |
} | |
// Ping the http if it's finished compaction | |
if (typeof callback === "function") { | |
ping(); | |
} | |
}); | |
}); | |
// Calls GET on the host, which gets back a JSON string containing | |
// couchdb: A welcome string | |
// version: The version of CouchDB it is running | |
api._info = function (callback) { | |
ajax({ | |
headers: host.headers, | |
method: 'GET', | |
url: genDBUrl(host, '') | |
}, function (err, res) { | |
if (err) { | |
callback(err); | |
} else { | |
res.host = genDBUrl(host, ''); | |
callback(null, res); | |
} | |
}); | |
}; | |
// Get the document with the given id from the database given by host. | |
// The id could be solely the _id in the database, or it may be a | |
// _design/ID or _local/ID path | |
api.get = utils.adapterFun('get', function (id, opts, callback) { | |
// If no options were given, set the callback to the second parameter | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
opts = utils.extend(true, {}, opts); | |
if (opts.auto_encode === undefined) { | |
opts.auto_encode = true; | |
} | |
// List of parameters to add to the GET request | |
var params = []; | |
// If it exists, add the opts.revs value to the list of parameters. | |
// If revs=true then the resulting JSON will include a field | |
// _revisions containing an array of the revision IDs. | |
if (opts.revs) { | |
params.push('revs=true'); | |
} | |
// If it exists, add the opts.revs_info value to the list of parameters. | |
// If revs_info=true then the resulting JSON will include the field | |
// _revs_info containing an array of objects in which each object | |
// representing an available revision. | |
if (opts.revs_info) { | |
params.push('revs_info=true'); | |
} | |
if (opts.local_seq) { | |
params.push('local_seq=true'); | |
} | |
// If it exists, add the opts.open_revs value to the list of parameters. | |
// If open_revs=all then the resulting JSON will include all the leaf | |
// revisions. If open_revs=["rev1", "rev2",...] then the resulting JSON | |
// will contain an array of objects containing data of all revisions | |
if (opts.open_revs) { | |
if (opts.open_revs !== "all") { | |
opts.open_revs = JSON.stringify(opts.open_revs); | |
} | |
params.push('open_revs=' + opts.open_revs); | |
} | |
// If it exists, add the opts.attachments value to the list of parameters. | |
// If attachments=true the resulting JSON will include the base64-encoded | |
// contents in the "data" property of each attachment. | |
if (opts.attachments) { | |
params.push('attachments=true'); | |
} | |
// If it exists, add the opts.rev value to the list of parameters. | |
// If rev is given a revision number then get the specified revision. | |
if (opts.rev) { | |
params.push('rev=' + opts.rev); | |
} | |
// If it exists, add the opts.conflicts value to the list of parameters. | |
// If conflicts=true then the resulting JSON will include the field | |
// _conflicts containing all the conflicting revisions. | |
if (opts.conflicts) { | |
params.push('conflicts=' + opts.conflicts); | |
} | |
// Format the list of parameters into a valid URI query string | |
params = params.join('&'); | |
params = params === '' ? '' : '?' + params; | |
if (opts.auto_encode) { | |
id = encodeDocId(id); | |
} | |
// Set the options for the ajax call | |
var options = { | |
headers: host.headers, | |
method: 'GET', | |
url: genDBUrl(host, id + params) | |
}; | |
// If the given id contains at least one '/' and the part before the '/' | |
// is NOT "_design" and is NOT "_local" | |
// OR | |
// If the given id contains at least two '/' and the part before the first | |
// '/' is "_design". | |
// TODO This second condition seems strange since if parts[0] === '_design' | |
// then we already know that parts[0] !== '_local'. | |
var parts = id.split('/'); | |
if ((parts.length > 1 && parts[0] !== '_design' && parts[0] !== '_local') || | |
(parts.length > 2 && parts[0] === '_design' && parts[0] !== '_local')) { | |
// Binary is expected back from the server | |
options.binary = true; | |
} | |
// Get the document | |
ajax(options, function (err, doc, xhr) { | |
// If the document does not exist, send an error to the callback | |
if (err) { | |
return callback(err); | |
} | |
// Send the document to the callback | |
callback(null, doc, xhr); | |
}); | |
}); | |
// Delete the document given by doc from the database given by host. | |
api.remove = utils.adapterFun('remove', function (doc, opts, callback) { | |
// If no options were given, set the callback to be the second parameter | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
// Delete the document | |
ajax({ | |
headers: host.headers, | |
method: 'DELETE', | |
url: genDBUrl(host, encodeDocId(doc._id)) + '?rev=' + doc._rev | |
}, callback); | |
}); | |
// Get the attachment | |
api.getAttachment = utils.adapterFun('getAttachment', function (docId, attachmentId, opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
opts = utils.extend(true, {}, opts); | |
if (opts.auto_encode === undefined) { | |
opts.auto_encode = true; | |
} | |
if (opts.auto_encode) { | |
docId = encodeDocId(docId); | |
} | |
opts.auto_encode = false; | |
api.get(docId + '/' + attachmentId, opts, callback); | |
}); | |
// Remove the attachment given by the id and rev | |
api.removeAttachment = utils.adapterFun('removeAttachment', function (docId, attachmentId, rev, callback) { | |
ajax({ | |
headers: host.headers, | |
method: 'DELETE', | |
url: genDBUrl(host, encodeDocId(docId) + '/' + attachmentId) + '?rev=' + rev | |
}, callback); | |
}); | |
// Add the attachment given by blob and its contentType property | |
// to the document with the given id, the revision given by rev, and | |
// add it to the database given by host. | |
api.putAttachment = utils.adapterFun('putAttachment', function (docId, attachmentId, rev, blob, type, callback) { | |
if (typeof type === 'function') { | |
callback = type; | |
type = blob; | |
blob = rev; | |
rev = null; | |
} | |
if (typeof type === 'undefined') { | |
type = blob; | |
blob = rev; | |
rev = null; | |
} | |
var id = encodeDocId(docId) + '/' + attachmentId; | |
var url = genDBUrl(host, id); | |
if (rev) { | |
url += '?rev=' + rev; | |
} | |
var opts = { | |
headers: host.headers, | |
method: 'PUT', | |
url: url, | |
processData: false, | |
body: blob, | |
timeout: 60000 | |
}; | |
opts.headers['Content-Type'] = type; | |
// Add the attachment | |
ajax(opts, callback); | |
}); | |
// Add the document given by doc (in JSON string format) to the database | |
// given by host. This fails if the doc has no _id field. | |
api.put = utils.adapterFun('put', utils.getArguments(function (args) { | |
var temp, temptype, opts, callback; | |
var doc = args.shift(); | |
var id = '_id' in doc; | |
if (typeof doc !== 'object' || Array.isArray(doc)) { | |
callback = args.pop(); | |
return callback(errors.NOT_AN_OBJECT); | |
} | |
doc = utils.extend(true, {}, doc); | |
while (true) { | |
temp = args.shift(); | |
temptype = typeof temp; | |
if (temptype === "string" && !id) { | |
doc._id = temp; | |
id = true; | |
} else if (temptype === "string" && id && !('_rev' in doc)) { | |
doc._rev = temp; | |
} else if (temptype === "object") { | |
opts = utils.extend(true, {}, temp); | |
} else if (temptype === "function") { | |
callback = temp; | |
} | |
if (!args.length) { | |
break; | |
} | |
} | |
opts = opts || {}; | |
var error = utils.invalidIdError(doc._id); | |
if (error) { | |
return callback(error); | |
} | |
// List of parameter to add to the PUT request | |
var params = []; | |
// If it exists, add the opts.new_edits value to the list of parameters. | |
// If new_edits = false then the database will NOT assign this document a | |
// new revision number | |
if (opts && typeof opts.new_edits !== 'undefined') { | |
params.push('new_edits=' + opts.new_edits); | |
} | |
// Format the list of parameters into a valid URI query string | |
params = params.join('&'); | |
if (params !== '') { | |
params = '?' + params; | |
} | |
// Add the document | |
ajax({ | |
headers: host.headers, | |
method: 'PUT', | |
url: genDBUrl(host, encodeDocId(doc._id)) + params, | |
body: doc | |
}, callback); | |
})); | |
// Add the document given by doc (in JSON string format) to the database | |
// given by host. This does not assume that doc is a new document (i.e. does not | |
// have a _id or a _rev field. | |
api.post = utils.adapterFun('post', function (doc, opts, callback) { | |
// If no options were given, set the callback to be the second parameter | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
opts = utils.extend(true, {}, opts); | |
if (typeof doc !== 'object') { | |
return callback(errors.NOT_AN_OBJECT); | |
} | |
if (! ("_id" in doc)) { | |
if (uuids.list.length > 0) { | |
doc._id = uuids.list.pop(); | |
api.put(doc, opts, callback); | |
} else { | |
uuids.get(function (err, resp) { | |
if (err) { | |
return callback(errors.UNKNOWN_ERROR); | |
} | |
doc._id = uuids.list.pop(); | |
api.put(doc, opts, callback); | |
}); | |
} | |
} else { | |
api.put(doc, opts, callback); | |
} | |
}); | |
// Update/create multiple documents given by req in the database | |
// given by host. | |
api.bulkDocs = utils.adapterFun('bulkDocs', function (req, opts, callback) { | |
// If no options were given, set the callback to be the second parameter | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
if (!opts) { | |
opts = {}; | |
} | |
if (!Array.isArray(req.docs)) { | |
return callback(errors.error(errors.NOT_AN_OBJECT, "Missing JSON list of 'docs'")); | |
} | |
var bad = req.docs.filter(function (doc) { | |
return typeof doc !== 'object' || Array.isArray(doc); | |
}); | |
if (bad.length) { | |
return callback(errors.NOT_AN_OBJECT); | |
} | |
req = utils.extend(true, {}, req); | |
opts = utils.extend(true, {}, opts); | |
// If opts.new_edits exists add it to the document data to be | |
// send to the database. | |
// If new_edits=false then it prevents the database from creating | |
// new revision numbers for the documents. Instead it just uses | |
// the old ones. This is used in database replication. | |
if (typeof opts.new_edits !== 'undefined') { | |
req.new_edits = opts.new_edits; | |
} | |
// Update/create the documents | |
ajax({ | |
headers: host.headers, | |
method: 'POST', | |
url: genDBUrl(host, '_bulk_docs'), | |
body: req | |
}, callback); | |
}); | |
// Get a listing of the documents in the database given | |
// by host and ordered by increasing id. | |
api.allDocs = utils.adapterFun('allDocs', function (opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
opts = utils.extend(true, {}, opts); | |
// List of parameters to add to the GET request | |
var params = []; | |
var body; | |
var method = 'GET'; | |
// TODO I don't see conflicts as a valid parameter for a | |
// _all_docs request (see http://wiki.apache.org/couchdb/HTTP_Document_API#all_docs) | |
if (opts.conflicts) { | |
params.push('conflicts=true'); | |
} | |
// If opts.descending is truthy add it to params | |
if (opts.descending) { | |
params.push('descending=true'); | |
} | |
// If opts.include_docs exists, add the include_docs value to the | |
// list of parameters. | |
// If include_docs=true then include the associated document with each | |
// result. | |
if (opts.include_docs) { | |
params.push('include_docs=true'); | |
} | |
if (opts.key) { | |
params.push('key=' + encodeURIComponent(JSON.stringify(opts.key))); | |
} | |
// If opts.startkey exists, add the startkey value to the list of | |
// parameters. | |
// If startkey is given then the returned list of documents will | |
// start with the document whose id is startkey. | |
if (opts.startkey) { | |
params.push('startkey=' + | |
encodeURIComponent(JSON.stringify(opts.startkey))); | |
} | |
// If opts.endkey exists, add the endkey value to the list of parameters. | |
// If endkey is given then the returned list of docuemnts will | |
// end with the document whose id is endkey. | |
if (opts.endkey) { | |
params.push('endkey=' + encodeURIComponent(JSON.stringify(opts.endkey))); | |
} | |
// If opts.limit exists, add the limit value to the parameter list. | |
if (typeof opts.limit !== 'undefined') { | |
params.push('limit=' + opts.limit); | |
} | |
if (typeof opts.skip !== 'undefined') { | |
params.push('skip=' + opts.skip); | |
} | |
// Format the list of parameters into a valid URI query string | |
params = params.join('&'); | |
if (params !== '') { | |
params = '?' + params; | |
} | |
if (typeof opts.keys !== 'undefined') { | |
var MAX_URL_LENGTH = 2000; | |
// according to http://stackoverflow.com/a/417184/680742, | |
// the de factor URL length limit is 2000 characters | |
var keysAsString = 'keys=' + encodeURIComponent(JSON.stringify(opts.keys)); | |
if (keysAsString.length + params.length + 1 <= MAX_URL_LENGTH) { | |
// If the keys are short enough, do a GET. we do this to work around | |
// Safari not understanding 304s on POSTs (see issue #1239) | |
params += (params.indexOf('?') !== -1 ? '&' : '?') + keysAsString; | |
} else { | |
// If keys are too long, issue a POST request to circumvent GET query string limits | |
// see http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options | |
method = 'POST'; | |
body = JSON.stringify({keys: opts.keys}); | |
} | |
} | |
// Get the document listing | |
ajax({ | |
headers: host.headers, | |
method: method, | |
url: genDBUrl(host, '_all_docs' + params), | |
body: body | |
}, callback); | |
}); | |
// Get a list of changes made to documents in the database given by host. | |
// TODO According to the README, there should be two other methods here, | |
// api.changes.addListener and api.changes.removeListener. | |
api._changes = function (opts) { | |
// We internally page the results of a changes request, this means | |
// if there is a large set of changes to be returned we can start | |
// processing them quicker instead of waiting on the entire | |
// set of changes to return and attempting to process them at once | |
var CHANGES_LIMIT = 25; | |
opts = utils.extend(true, {}, opts); | |
opts.timeout = opts.timeout || 0; | |
var params = {}; | |
var limit = (typeof opts.limit !== 'undefined') ? opts.limit : false; | |
if (limit === 0) { | |
limit = 1; | |
} | |
// | |
var leftToFetch = limit; | |
if (opts.style) { | |
params.style = opts.style; | |
} | |
if (opts.include_docs || opts.filter && typeof opts.filter === 'function') { | |
params.include_docs = true; | |
} | |
if (opts.continuous) { | |
params.feed = 'longpoll'; | |
} | |
if (opts.conflicts) { | |
params.conflicts = true; | |
} | |
if (opts.descending) { | |
params.descending = true; | |
} | |
if (opts.filter && typeof opts.filter === 'string') { | |
params.filter = opts.filter; | |
if (opts.filter === '_view' && opts.view && typeof opts.view === 'string') { | |
params.view = opts.view; | |
} | |
} | |
// If opts.query_params exists, pass it through to the changes request. | |
// These parameters may be used by the filter on the source database. | |
if (opts.query_params && typeof opts.query_params === 'object') { | |
for (var param_name in opts.query_params) { | |
if (opts.query_params.hasOwnProperty(param_name)) { | |
params[param_name] = opts.query_params[param_name]; | |
} | |
} | |
} | |
var xhr; | |
var lastFetchedSeq; | |
// Get all the changes starting wtih the one immediately after the | |
// sequence number given by since. | |
var fetch = function (since, callback) { | |
if (opts.aborted) { | |
return; | |
} | |
params.since = since; | |
if (opts.descending) { | |
if (limit) { | |
params.limit = leftToFetch; | |
} | |
} else { | |
params.limit = (!limit || leftToFetch > CHANGES_LIMIT) ? | |
CHANGES_LIMIT : leftToFetch; | |
} | |
var paramStr = '?' + Object.keys(params).map(function (k) { | |
return k + '=' + params[k]; | |
}).join('&'); | |
// Set the options for the ajax call | |
var xhrOpts = { | |
headers: host.headers, | |
method: 'GET', | |
url: genDBUrl(host, '_changes' + paramStr), | |
// _changes can take a long time to generate, especially when filtered | |
timeout: opts.timeout | |
}; | |
lastFetchedSeq = since; | |
if (opts.aborted) { | |
return; | |
} | |
// Get the changes | |
xhr = ajax(xhrOpts, callback); | |
}; | |
// If opts.since exists, get all the changes from the sequence | |
// number given by opts.since. Otherwise, get all the changes | |
// from the sequence number 0. | |
var fetchTimeout = 10; | |
var fetchRetryCount = 0; | |
var results = {results: []}; | |
var fetched = function (err, res) { | |
if (opts.aborted) { | |
return; | |
} | |
var raw_results_length = 0; | |
// If the result of the ajax call (res) contains changes (res.results) | |
if (res && res.results) { | |
raw_results_length = res.results.length; | |
results.last_seq = res.last_seq; | |
// For each change | |
var req = {}; | |
req.query = opts.query_params; | |
res.results = res.results.filter(function (c) { | |
leftToFetch--; | |
var ret = utils.filterChange(opts)(c); | |
if (ret) { | |
results.results.push(c); | |
utils.call(opts.onChange, c); | |
} | |
return ret; | |
}); | |
} else if (err) { | |
// In case of an error, stop listening for changes and call opts.complete | |
opts.aborted = true; | |
utils.call(opts.complete, err); | |
return; | |
} | |
// The changes feed may have timed out with no results | |
// if so reuse last update sequence | |
if (res && res.last_seq) { | |
lastFetchedSeq = res.last_seq; | |
} | |
var finished = (limit && leftToFetch <= 0) || | |
(res && raw_results_length < CHANGES_LIMIT) || | |
(opts.descending); | |
if (opts.continuous || !finished) { | |
// Increase retry delay exponentially as long as errors persist | |
if (err) { | |
fetchRetryCount += 1; | |
} else { | |
fetchRetryCount = 0; | |
} | |
var timeoutMultiplier = 1 << fetchRetryCount; | |
var retryWait = fetchTimeout * timeoutMultiplier; | |
var maximumWait = opts.maximumWait || 30000; | |
if (retryWait > maximumWait) { | |
utils.call(opts.complete, err || errors.UNKNOWN_ERROR); | |
return; | |
} | |
// Queue a call to fetch again with the newest sequence number | |
setTimeout(function () { fetch(lastFetchedSeq, fetched); }, retryWait); | |
} else { | |
// We're done, call the callback | |
utils.call(opts.complete, null, results); | |
} | |
}; | |
fetch(opts.since || 0, fetched); | |
// Return a method to cancel this method from processing any more | |
return { | |
cancel: function () { | |
opts.aborted = true; | |
xhr.abort(); | |
} | |
}; | |
}; | |
// Given a set of document/revision IDs (given by req), tets the subset of | |
// those that do NOT correspond to revisions stored in the database. | |
// See http://wiki.apache.org/couchdb/HttpPostRevsDiff | |
api.revsDiff = utils.adapterFun('revsDif', function (req, opts, callback) { | |
// If no options were given, set the callback to be the second parameter | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
// Get the missing document/revision IDs | |
ajax({ | |
headers: host.headers, | |
method: 'POST', | |
url: genDBUrl(host, '_revs_diff'), | |
body: req | |
}, function (err, res) { | |
callback(err, res); | |
}); | |
}); | |
api.close = utils.adapterFun('close', function (callback) { | |
callback(); | |
}); | |
function replicateOnServer(target, opts, promise, targetHostUrl) { | |
opts = utils.extend(true, {}, opts); | |
var targetHost = api.getHost(targetHostUrl); | |
var params = { | |
source: host.db, | |
target: targetHost.protocol === host.protocol && | |
targetHost.authority === host.authority ? targetHost.db : targetHost.source | |
}; | |
if (opts.continuous) { | |
params.continuous = true; | |
} | |
if (opts.create_target) { | |
params.create_target = true; | |
} | |
if (opts.doc_ids) { | |
params.doc_ids = opts.doc_ids; | |
} | |
if (opts.filter && typeof opts.filter === 'string') { | |
params.filter = opts.filter; | |
} | |
if (opts.query_params) { | |
params.query_params = opts.query_params; | |
} | |
var result = {}; | |
var repOpts = { | |
headers: host.headers, | |
method: 'POST', | |
url: genUrl(host, '_replicate'), | |
body: params | |
}; | |
var xhr; | |
promise.cancel = function () { | |
this.cancelled = true; | |
if (xhr && !result.ok) { | |
xhr.abort(); | |
} | |
if (result._local_id) { | |
repOpts.body = { | |
replication_id: result._local_id | |
}; | |
} | |
repOpts.body.cancel = true; | |
ajax(repOpts, function (err, resp, xhr) { | |
// If the replication cancel request fails, send an error to the callback | |
if (err) { | |
return callback(err); | |
} | |
// Send the replication cancel result to the complete callback | |
utils.call(opts.complete, null, result, xhr); | |
}); | |
}; | |
if (promise.cancelled) { | |
return; | |
} | |
xhr = ajax(repOpts, function (err, resp, xhr) { | |
// If the replication fails, send an error to the callback | |
if (err) { | |
return callback(err); | |
} | |
result.ok = true; | |
// Provided by CouchDB from 1.2.0 onward to cancel replication | |
if (resp._local_id) { | |
result._local_id = resp._local_id; | |
} | |
// Send the replication result to the complete callback | |
utils.call(opts.complete, null, resp, xhr); | |
}); | |
} | |
api.replicateOnServer = function (target, opts, promise) { | |
if (!api.taskqueue.isReady) { | |
api.taskqueue.addTask('replicateOnServer', [target, opts, promise]); | |
return promise; | |
} | |
target.info(function (err, info) { | |
replicateOnServer(target, opts, promise, info.host); | |
}); | |
}; | |
api.destroy = utils.adapterFun('destroy', function (callback) { | |
ajax({ | |
url: genDBUrl(host, ''), | |
method: 'DELETE' | |
}, function (err, resp) { | |
if (err) { | |
api.emit('error', err); | |
callback(err); | |
} else { | |
api.emit('destroyed'); | |
callback(null, resp); | |
} | |
}); | |
}); | |
} | |
// Delete the HttpPouch specified by the given name. | |
HttpPouch.destroy = utils.toPromise(function (name, opts, callback) { | |
var host = getHost(name, opts); | |
opts = opts || {}; | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
opts = utils.extend(true, {}, opts); | |
opts.headers = host.headers; | |
opts.method = 'DELETE'; | |
opts.url = genDBUrl(host, ''); | |
var ajaxOpts = opts.ajax || {}; | |
opts = utils.extend({}, opts, ajaxOpts); | |
utils.ajax(opts, callback); | |
}); | |
// HttpPouch is a valid adapter. | |
HttpPouch.valid = function () { | |
return true; | |
}; | |
module.exports = HttpPouch; | |
},{"../deps/errors":8,"../utils":18}],3:[function(_dereq_,module,exports){ | |
(function (global){ | |
'use strict'; | |
var utils = _dereq_('../utils'); | |
var merge = _dereq_('../merge'); | |
var errors = _dereq_('../deps/errors'); | |
function idbError(callback) { | |
return function (event) { | |
callback(errors.error(errors.IDB_ERROR, event.target, event.type)); | |
}; | |
} | |
function isModernIdb() { | |
// check for outdated implementations of IDB | |
// that rely on the setVersion method instead of onupgradeneeded (issue #1207) | |
// cache based on appVersion, in case the browser is updated | |
var cacheKey = "_pouch__checkModernIdb_" + | |
(global.navigator && global.navigator.appVersion); | |
var cached = utils.hasLocalStorage() && global.localStorage[cacheKey]; | |
if (cached) { | |
return JSON.parse(cached); | |
} | |
var dbName = '_pouch__checkModernIdb'; | |
var result = global.indexedDB.open(dbName, 1).onupgradeneeded === null; | |
if (global.indexedDB.deleteDatabase) { | |
global.indexedDB.deleteDatabase(dbName); // db no longer needed | |
} | |
if (utils.hasLocalStorage()) { | |
global.localStorage[cacheKey] = JSON.stringify(result); // cache | |
} | |
return result; | |
} | |
function IdbPouch(opts, callback) { | |
// IndexedDB requires a versioned database structure, so we use the | |
// version here to manage migrations. | |
var ADAPTER_VERSION = 2; | |
// The object stores created for each database | |
// DOC_STORE stores the document meta data, its revision history and state | |
var DOC_STORE = 'document-store'; | |
// BY_SEQ_STORE stores a particular version of a document, keyed by its | |
// sequence id | |
var BY_SEQ_STORE = 'by-sequence'; | |
// Where we store attachments | |
var ATTACH_STORE = 'attach-store'; | |
// Where we store meta data | |
var META_STORE = 'meta-store'; | |
// Where we detect blob support | |
var DETECT_BLOB_SUPPORT_STORE = 'detect-blob-support'; | |
var name = opts.name; | |
var req = global.indexedDB.open(name, ADAPTER_VERSION); | |
if (!('openReqList' in IdbPouch)) { | |
IdbPouch.openReqList = {}; | |
} | |
IdbPouch.openReqList[name] = req; | |
var blobSupport = null; | |
var instanceId = null; | |
var api = this; | |
var idb = null; | |
req.onupgradeneeded = function (e) { | |
var db = e.target.result; | |
if (e.oldVersion < 1) { | |
// initial schema | |
createSchema(db); | |
} | |
if (e.oldVersion < 2) { | |
// version 2 adds the deletedOrLocal index | |
addDeletedOrLocalIndex(e); | |
} | |
}; | |
function createSchema(db) { | |
db.createObjectStore(DOC_STORE, {keyPath : 'id'}) | |
.createIndex('seq', 'seq', {unique: true}); | |
db.createObjectStore(BY_SEQ_STORE, {autoIncrement: true}) | |
.createIndex('_doc_id_rev', '_doc_id_rev', {unique: true}); | |
db.createObjectStore(ATTACH_STORE, {keyPath: 'digest'}); | |
db.createObjectStore(META_STORE, {keyPath: 'id', autoIncrement: false}); | |
db.createObjectStore(DETECT_BLOB_SUPPORT_STORE); | |
} | |
function addDeletedOrLocalIndex(e) { | |
var docStore = e.currentTarget.transaction.objectStore(DOC_STORE); | |
docStore.openCursor().onsuccess = function (event) { | |
var cursor = event.target.result; | |
if (cursor) { | |
var metadata = cursor.value; | |
var deleted = utils.isDeleted(metadata); | |
var local = utils.isLocalId(metadata.id); | |
metadata.deletedOrLocal = (deleted || local) ? "1" : "0"; | |
docStore.put(metadata); | |
cursor['continue'](); | |
} else { | |
docStore.createIndex('deletedOrLocal', 'deletedOrLocal', {unique : false}); | |
} | |
}; | |
} | |
req.onsuccess = function (e) { | |
idb = e.target.result; | |
idb.onversionchange = function () { | |
idb.close(); | |
}; | |
var txn = idb.transaction([META_STORE, DETECT_BLOB_SUPPORT_STORE], | |
'readwrite'); | |
var req = txn.objectStore(META_STORE).get(META_STORE); | |
req.onsuccess = function (e) { | |
var idStored = false; | |
var checkSetupComplete = function () { | |
if (blobSupport === null || !idStored) { | |
return; | |
} else { | |
callback(null, api); | |
} | |
}; | |
var meta = e.target.result || {id: META_STORE}; | |
if (name + '_id' in meta) { | |
instanceId = meta[name + '_id']; | |
idStored = true; | |
checkSetupComplete(); | |
} else { | |
instanceId = utils.uuid(); | |
meta[name + '_id'] = instanceId; | |
txn.objectStore(META_STORE).put(meta).onsuccess = function () { | |
idStored = true; | |
checkSetupComplete(); | |
}; | |
} | |
// detect blob support | |
try { | |
txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(utils.createBlob(), "key"); | |
blobSupport = true; | |
} catch (err) { | |
blobSupport = false; | |
} finally { | |
checkSetupComplete(); | |
} | |
}; | |
}; | |
req.onerror = idbError(callback); | |
api.type = function () { | |
return 'idb'; | |
}; | |
api._id = utils.toPromise(function (callback) { | |
callback(null, instanceId); | |
}); | |
api._bulkDocs = function idb_bulkDocs(req, opts, callback) { | |
var newEdits = opts.new_edits; | |
var userDocs = req.docs; | |
// Parse the docs, give them a sequence number for the result | |
var docInfos = userDocs.map(function (doc, i) { | |
var newDoc = utils.parseDoc(doc, newEdits); | |
newDoc._bulk_seq = i; | |
return newDoc; | |
}); | |
var docInfoErrors = docInfos.filter(function (docInfo) { | |
return docInfo.error; | |
}); | |
if (docInfoErrors.length) { | |
return callback(docInfoErrors[0]); | |
} | |
var results = []; | |
var docsWritten = 0; | |
function writeMetaData(e) { | |
var meta = e.target.result; | |
meta.updateSeq = (meta.updateSeq || 0) + docsWritten; | |
txn.objectStore(META_STORE).put(meta); | |
} | |
function processDocs() { | |
if (!docInfos.length) { | |
txn.objectStore(META_STORE).get(META_STORE).onsuccess = writeMetaData; | |
return; | |
} | |
var currentDoc = docInfos.shift(); | |
var req = txn.objectStore(DOC_STORE).get(currentDoc.metadata.id); | |
req.onsuccess = function process_docRead(event) { | |
var oldDoc = event.target.result; | |
if (!oldDoc) { | |
insertDoc(currentDoc); | |
} else { | |
updateDoc(oldDoc, currentDoc); | |
} | |
}; | |
} | |
function complete(event) { | |
var aresults = []; | |
results.sort(sortByBulkSeq); | |
results.forEach(function (result) { | |
delete result._bulk_seq; | |
if (result.error) { | |
aresults.push(result); | |
return; | |
} | |
var metadata = result.metadata; | |
var rev = merge.winningRev(metadata); | |
aresults.push({ | |
ok: true, | |
id: metadata.id, | |
rev: rev | |
}); | |
if (utils.isLocalId(metadata.id)) { | |
return; | |
} | |
IdbPouch.Changes.notify(name); | |
IdbPouch.Changes.notifyLocalWindows(name); | |
}); | |
callback(null, aresults); | |
} | |
function preprocessAttachment(att, finish) { | |
if (att.stub) { | |
return finish(); | |
} | |
if (typeof att.data === 'string') { | |
var data; | |
try { | |
data = atob(att.data); | |
} catch (e) { | |
var err = errors.error(errors.BAD_ARG, | |
"Attachments need to be base64 encoded"); | |
return callback(err); | |
} | |
att.digest = 'md5-' + utils.Crypto.MD5(data); | |
if (blobSupport) { | |
var type = att.content_type; | |
data = utils.fixBinary(data); | |
att.data = utils.createBlob([data], {type: type}); | |
} | |
return finish(); | |
} | |
var reader = new FileReader(); | |
reader.onloadend = function (e) { | |
var binary = utils.arrayBufferToBinaryString(this.result); | |
att.digest = 'md5-' + utils.Crypto.MD5(binary); | |
if (!blobSupport) { | |
att.data = btoa(binary); | |
} | |
finish(); | |
}; | |
reader.readAsArrayBuffer(att.data); | |
} | |
function preprocessAttachments(callback) { | |
if (!docInfos.length) { | |
return callback(); | |
} | |
var docv = 0; | |
docInfos.forEach(function (docInfo) { | |
var attachments = docInfo.data && docInfo.data._attachments ? | |
Object.keys(docInfo.data._attachments) : []; | |
if (!attachments.length) { | |
return done(); | |
} | |
var recv = 0; | |
function attachmentProcessed() { | |
recv++; | |
if (recv === attachments.length) { | |
done(); | |
} | |
} | |
for (var key in docInfo.data._attachments) { | |
if (docInfo.data._attachments.hasOwnProperty(key)) { | |
preprocessAttachment(docInfo.data._attachments[key], attachmentProcessed); | |
} | |
} | |
}); | |
function done() { | |
docv++; | |
if (docInfos.length === docv) { | |
callback(); | |
} | |
} | |
} | |
function writeDoc(docInfo, callback) { | |
var err = null; | |
var recv = 0; | |
docInfo.data._id = docInfo.metadata.id; | |
docInfo.data._rev = docInfo.metadata.rev; | |
docsWritten++; | |
if (utils.isDeleted(docInfo.metadata, docInfo.metadata.rev)) { | |
docInfo.data._deleted = true; | |
} | |
var attachments = docInfo.data._attachments ? | |
Object.keys(docInfo.data._attachments) : []; | |
function collectResults(attachmentErr) { | |
if (!err) { | |
if (attachmentErr) { | |
err = attachmentErr; | |
callback(err); | |
} else if (recv === attachments.length) { | |
finish(); | |
} | |
} | |
} | |
function attachmentSaved(err) { | |
recv++; | |
collectResults(err); | |
} | |
for (var key in docInfo.data._attachments) { | |
if (!docInfo.data._attachments[key].stub) { | |
var data = docInfo.data._attachments[key].data; | |
delete docInfo.data._attachments[key].data; | |
var digest = docInfo.data._attachments[key].digest; | |
saveAttachment(docInfo, digest, data, attachmentSaved); | |
} else { | |
recv++; | |
collectResults(); | |
} | |
} | |
function finish() { | |
docInfo.data._doc_id_rev = docInfo.data._id + "::" + docInfo.data._rev; | |
var index = txn.objectStore(BY_SEQ_STORE).index('_doc_id_rev'); | |
index.getKey(docInfo.data._doc_id_rev).onsuccess = function (e) { | |
var dataReq = e.target.result ? | |
txn.objectStore(BY_SEQ_STORE).put(docInfo.data, e.target.result) : | |
txn.objectStore(BY_SEQ_STORE).put(docInfo.data); | |
dataReq.onsuccess = function (e) { | |
docInfo.metadata.seq = e.target.result; | |
// Current _rev is calculated from _rev_tree on read | |
delete docInfo.metadata.rev; | |
var deleted = utils.isDeleted(docInfo.metadata); | |
var local = utils.isLocalId(docInfo.metadata.id); | |
var metadata = utils.extend(true, { | |
deletedOrLocal : (deleted || local) ? "1" : "0" | |
}, docInfo.metadata); | |
var metaDataReq = txn.objectStore(DOC_STORE).put(metadata); | |
metaDataReq.onsuccess = function () { | |
results.push(docInfo); | |
utils.call(callback); | |
}; | |
}; | |
}; | |
} | |
if (!attachments.length) { | |
finish(); | |
} | |
} | |
function updateDoc(oldDoc, docInfo) { | |
var merged = merge.merge(oldDoc.rev_tree, docInfo.metadata.rev_tree[0], 1000); | |
var wasPreviouslyDeleted = utils.isDeleted(oldDoc); | |
var inConflict = (wasPreviouslyDeleted && | |
utils.isDeleted(docInfo.metadata)) || | |
(!wasPreviouslyDeleted && newEdits && merged.conflicts !== 'new_leaf'); | |
if (inConflict) { | |
results.push(makeErr(errors.REV_CONFLICT, docInfo._bulk_seq)); | |
return processDocs(); | |
} | |
docInfo.metadata.rev_tree = merged.tree; | |
writeDoc(docInfo, processDocs); | |
} | |
function insertDoc(docInfo) { | |
// Cant insert new deleted documents | |
if ('was_delete' in opts && utils.isDeleted(docInfo.metadata)) { | |
results.push(errors.MISSING_DOC); | |
return processDocs(); | |
} | |
writeDoc(docInfo, processDocs); | |
} | |
// Insert sequence number into the error so we can sort later | |
function makeErr(err, seq) { | |
err._bulk_seq = seq; | |
return err; | |
} | |
function saveAttachment(docInfo, digest, data, callback) { | |
var objectStore = txn.objectStore(ATTACH_STORE); | |
objectStore.get(digest).onsuccess = function (e) { | |
var originalRefs = e.target.result && e.target.result.refs || {}; | |
var ref = [docInfo.metadata.id, docInfo.metadata.rev].join('@'); | |
var newAtt = { | |
digest: digest, | |
body: data, | |
refs: originalRefs | |
}; | |
newAtt.refs[ref] = true; | |
objectStore.put(newAtt).onsuccess = function (e) { | |
utils.call(callback); | |
}; | |
}; | |
} | |
var txn; | |
preprocessAttachments(function () { | |
txn = idb.transaction([DOC_STORE, BY_SEQ_STORE, ATTACH_STORE, META_STORE], | |
'readwrite'); | |
txn.onerror = idbError(callback); | |
txn.ontimeout = idbError(callback); | |
txn.oncomplete = complete; | |
processDocs(); | |
}); | |
}; | |
function sortByBulkSeq(a, b) { | |
return a._bulk_seq - b._bulk_seq; | |
} | |
// First we look up the metadata in the ids database, then we fetch the | |
// current revision(s) from the by sequence store | |
api._get = function idb_get(id, opts, callback) { | |
var doc; | |
var metadata; | |
var err; | |
var txn; | |
opts = utils.extend(true, {}, opts); | |
if (opts.ctx) { | |
txn = opts.ctx; | |
} else { | |
txn = idb.transaction([DOC_STORE, BY_SEQ_STORE, ATTACH_STORE], 'readonly'); | |
} | |
function finish() { | |
callback(err, {doc: doc, metadata: metadata, ctx: txn}); | |
} | |
txn.objectStore(DOC_STORE).get(id).onsuccess = function (e) { | |
metadata = e.target.result; | |
// we can determine the result here if: | |
// 1. there is no such document | |
// 2. the document is deleted and we don't ask about specific rev | |
// When we ask with opts.rev we expect the answer to be either | |
// doc (possibly with _deleted=true) or missing error | |
if (!metadata) { | |
err = errors.MISSING_DOC; | |
return finish(); | |
} | |
if (utils.isDeleted(metadata) && !opts.rev) { | |
err = errors.error(errors.MISSING_DOC, "deleted"); | |
return finish(); | |
} | |
var rev = merge.winningRev(metadata); | |
var key = metadata.id + '::' + (opts.rev ? opts.rev : rev); | |
var index = txn.objectStore(BY_SEQ_STORE).index('_doc_id_rev'); | |
index.get(key).onsuccess = function (e) { | |
doc = e.target.result; | |
if (doc && doc._doc_id_rev) { | |
delete(doc._doc_id_rev); | |
} | |
if (!doc) { | |
err = errors.MISSING_DOC; | |
return finish(); | |
} | |
finish(); | |
}; | |
}; | |
}; | |
api._getAttachment = function (attachment, opts, callback) { | |
var result; | |
var txn; | |
opts = utils.extend(true, {}, opts); | |
if (opts.ctx) { | |
txn = opts.ctx; | |
} else { | |
txn = idb.transaction([DOC_STORE, BY_SEQ_STORE, ATTACH_STORE], 'readonly'); | |
} | |
var digest = attachment.digest; | |
var type = attachment.content_type; | |
txn.objectStore(ATTACH_STORE).get(digest).onsuccess = function (e) { | |
var data = e.target.result.body; | |
if (opts.encode) { | |
if (blobSupport) { | |
var reader = new FileReader(); | |
reader.onloadend = function (e) { | |
var binary = utils.arrayBufferToBinaryString(this.result); | |
result = btoa(binary); | |
callback(null, result); | |
}; | |
reader.readAsArrayBuffer(data); | |
} else { | |
result = data; | |
callback(null, result); | |
} | |
} else { | |
if (blobSupport) { | |
result = data; | |
} else { | |
data = utils.fixBinary(atob(data)); | |
result = utils.createBlob([data], {type: type}); | |
} | |
callback(null, result); | |
} | |
}; | |
}; | |
function allDocsKeysQuery(totalRows, opts, callback) { | |
var keys = opts.keys; | |
var descending = 'descending' in opts ? opts.descending : false; | |
if (!keys.length) { // empty list is okay | |
callback(null, { | |
offset : opts.skip, | |
rows : [], | |
total_rows : totalRows | |
}); | |
} else { | |
// do a separate "key" query for each key in the keys array | |
var resultsToCollate = []; | |
keys.forEach(function (key) { | |
var subOpts = utils.extend(true, {}, opts); | |
subOpts.keys_request = true; // internal param, says this is a "keys" request | |
subOpts.key = key; | |
delete subOpts.keys; | |
delete subOpts.skip; | |
delete subOpts.limit; | |
allDocsNormalQuery(totalRows, subOpts, function (err, res) { | |
resultsToCollate.push({err : err, res : res, key : key}); | |
if (resultsToCollate.length === keys.length) { | |
// all done, time to collate | |
var keysToResults = {}; | |
for (var i = 0; i < resultsToCollate.length; i++) { | |
var result = resultsToCollate[i]; | |
if (result.err) { | |
callback(err); | |
return; | |
} else { | |
keysToResults[result.key] = result; | |
} | |
} | |
var results = []; | |
keys.forEach(function (key) { | |
var result = keysToResults[key]; | |
if (result.res.rows.length) { | |
results.push(result.res.rows[0]); // only one result ever | |
} else { | |
results.push({"key": key, "error": "not_found"}); | |
} | |
}); | |
if (descending) { | |
results = results.reverse(); | |
} | |
callback(null, { | |
total_rows: totalRows, | |
offset: opts.skip, | |
rows: ('limit' in opts) ? results.slice(opts.skip, opts.limit + opts.skip) : | |
(opts.skip > 0) ? results.slice(opts.skip) : results | |
}); | |
} | |
}); | |
}); | |
} | |
} | |
function allDocsNormalQuery(totalRows, opts, callback) { | |
var start = 'startkey' in opts ? opts.startkey : false; | |
var end = 'endkey' in opts ? opts.endkey : false; | |
var key = 'key' in opts ? opts.key : false; | |
var skip = opts.skip || 0; | |
var limit = typeof opts.limit === 'number' ? opts.limit : -1; | |
var descending = 'descending' in opts && opts.descending ? 'prev' : null; | |
var manualDescEnd = false; | |
if (descending && start && end) { | |
// unfortunately IDB has a quirk where IDBKeyRange.bound is invalid if the | |
// start is less than the end, even in descending mode. Best bet | |
// is just to handle it manually in that case. | |
manualDescEnd = end; | |
end = false; | |
} | |
var keyRange; | |
try { | |
keyRange = start && end ? global.IDBKeyRange.bound(start, end) | |
: start ? (descending ? global.IDBKeyRange.upperBound(start) : global.IDBKeyRange.lowerBound(start)) | |
: end ? (descending ? global.IDBKeyRange.lowerBound(end) : global.IDBKeyRange.upperBound(end)) | |
: key ? global.IDBKeyRange.only(key) : null; | |
} catch (e) { | |
if (e.name === "DataError" && e.code === 0) { | |
// data error, start is less than end | |
return callback(null, { | |
total_rows : totalRows, | |
offset : opts.skip, | |
rows : [] | |
}); | |
} else { | |
return callback(errors.error(errors.IDB_ERROR, e.name, e.message)); | |
} | |
} | |
var transaction = idb.transaction([DOC_STORE, BY_SEQ_STORE], 'readonly'); | |
transaction.oncomplete = function () { | |
callback(null, { | |
total_rows: totalRows, | |
offset: opts.skip, | |
rows: results | |
}); | |
}; | |
var oStore = transaction.objectStore(DOC_STORE); | |
var oCursor = descending ? oStore.openCursor(keyRange, descending) | |
: oStore.openCursor(keyRange); | |
var results = []; | |
oCursor.onsuccess = function (e) { | |
if (!e.target.result) { | |
return; | |
} | |
var cursor = e.target.result; | |
var metadata = cursor.value; | |
function allDocsInner(metadata, data) { | |
if (utils.isLocalId(metadata.id)) { | |
return cursor['continue'](); | |
} | |
var doc = { | |
id: metadata.id, | |
key: metadata.id, | |
value: { | |
rev: merge.winningRev(metadata) | |
} | |
}; | |
if (opts.include_docs) { | |
doc.doc = data; | |
doc.doc._rev = merge.winningRev(metadata); | |
if (doc.doc._doc_id_rev) { | |
delete(doc.doc._doc_id_rev); | |
} | |
if (opts.conflicts) { | |
doc.doc._conflicts = merge.collectConflicts(metadata); | |
} | |
for (var att in doc.doc._attachments) { | |
if (doc.doc._attachments.hasOwnProperty(att)) { | |
doc.doc._attachments[att].stub = true; | |
} | |
} | |
} | |
if (opts.keys_request) { | |
// deleted docs are okay with keys_requests | |
if (utils.isDeleted(metadata)) { | |
doc.value.deleted = true; | |
doc.doc = null; | |
} | |
results.push(doc); | |
} else if (!utils.isDeleted(metadata) && skip-- <= 0) { | |
if (manualDescEnd && doc.key < manualDescEnd) { | |
return; | |
} | |
results.push(doc); | |
if (--limit === 0) { | |
return; | |
} | |
} | |
cursor['continue'](); | |
} | |
if (!opts.include_docs) { | |
allDocsInner(metadata); | |
} else { | |
var index = transaction.objectStore(BY_SEQ_STORE).index('_doc_id_rev'); | |
var mainRev = merge.winningRev(metadata); | |
var key = metadata.id + "::" + mainRev; | |
index.get(key).onsuccess = function (event) { | |
allDocsInner(cursor.value, event.target.result); | |
}; | |
} | |
}; | |
} | |
api._allDocs = function idb_allDocs(opts, callback) { | |
// first count the total_rows using the undeleted/non-local count | |
var txn = idb.transaction([DOC_STORE], 'readonly'); | |
var totalRows; | |
function countUndeletedNonlocalDocs(e) { | |
totalRows = e.target.result; | |
} | |
var index = txn.objectStore(DOC_STORE).index('deletedOrLocal'); | |
index.count(global.IDBKeyRange.only("0")).onsuccess = countUndeletedNonlocalDocs; | |
txn.onerror = idbError(callback); | |
txn.oncomplete = function () { | |
if (opts.limit === 0) { | |
return callback(null, { | |
total_rows : totalRows, | |
offset : opts.skip, | |
rows : [] | |
}); | |
} else if ('keys' in opts) { | |
allDocsKeysQuery(totalRows, opts, callback); | |
} else { | |
allDocsNormalQuery(totalRows, opts, callback); | |
} | |
}; | |
}; | |
api._info = function idb_info(callback) { | |
var count = 0; | |
var update_seq = 0; | |
var txn = idb.transaction([DOC_STORE, META_STORE], 'readonly'); | |
function fetchUpdateSeq(e) { | |
update_seq = e.target.result && e.target.result.updateSeq || 0; | |
} | |
function countDocs(e) { | |
var cursor = e.target.result; | |
if (!cursor) { | |
txn.objectStore(META_STORE).get(META_STORE).onsuccess = fetchUpdateSeq; | |
return; | |
} | |
if (cursor.value.deleted !== true) { | |
count++; | |
} | |
cursor['continue'](); | |
} | |
txn.oncomplete = function () { | |
callback(null, { | |
db_name: name, | |
doc_count: count, | |
update_seq: update_seq | |
}); | |
}; | |
txn.objectStore(DOC_STORE).openCursor().onsuccess = countDocs; | |
}; | |
api._changes = function idb_changes(opts) { | |
opts = utils.extend(true, {}, opts); | |
if (opts.continuous) { | |
var id = name + ':' + utils.uuid(); | |
IdbPouch.Changes.addListener(name, id, api, opts); | |
IdbPouch.Changes.notify(name); | |
return { | |
cancel: function () { | |
IdbPouch.Changes.removeListener(name, id); | |
} | |
}; | |
} | |
var descending = opts.descending ? 'prev' : null; | |
var last_seq = 0; | |
// Ignore the `since` parameter when `descending` is true | |
opts.since = opts.since && !descending ? opts.since : 0; | |
var results = [], resultIndices = {}, dedupResults = []; | |
var txn; | |
function fetchChanges() { | |
txn = idb.transaction([DOC_STORE, BY_SEQ_STORE]); | |
txn.oncomplete = onTxnComplete; | |
var req; | |
if (descending) { | |
req = txn.objectStore(BY_SEQ_STORE) | |
.openCursor(global.IDBKeyRange.lowerBound(opts.since, true), descending); | |
} else { | |
req = txn.objectStore(BY_SEQ_STORE) | |
.openCursor(global.IDBKeyRange.lowerBound(opts.since, true)); | |
} | |
req.onsuccess = onsuccess; | |
req.onerror = onerror; | |
} | |
fetchChanges(); | |
function onsuccess(event) { | |
if (!event.target.result) { | |
// Filter out null results casued by deduping | |
for (var i = 0, l = results.length; i < l; i++) { | |
var result = results[i]; | |
if (result) { | |
dedupResults.push(result); | |
} | |
} | |
return false; | |
} | |
var cursor = event.target.result; | |
// Try to pre-emptively dedup to save us a bunch of idb calls | |
var changeId = cursor.value._id; | |
var changeIdIndex = resultIndices[changeId]; | |
if (changeIdIndex !== undefined) { | |
results[changeIdIndex].seq = cursor.key; | |
// update so it has the later sequence number | |
results.push(results[changeIdIndex]); | |
results[changeIdIndex] = null; | |
resultIndices[changeId] = results.length - 1; | |
return cursor['continue'](); | |
} | |
var index = txn.objectStore(DOC_STORE); | |
index.get(cursor.value._id).onsuccess = function (event) { | |
var metadata = event.target.result; | |
if (utils.isLocalId(metadata.id)) { | |
return cursor['continue'](); | |
} | |
if (last_seq < metadata.seq) { | |
last_seq = metadata.seq; | |
} | |
var mainRev = merge.winningRev(metadata); | |
var key = metadata.id + "::" + mainRev; | |
var index = txn.objectStore(BY_SEQ_STORE).index('_doc_id_rev'); | |
index.get(key).onsuccess = function (docevent) { | |
var doc = docevent.target.result; | |
delete doc['_doc_id_rev']; | |
doc._rev = mainRev; | |
var change = opts.processChange(doc, metadata, opts); | |
change.seq = cursor.key; | |
// Dedupe the changes feed | |
var changeId = change.id, changeIdIndex = resultIndices[changeId]; | |
if (changeIdIndex !== undefined) { | |
results[changeIdIndex] = null; | |
} | |
results.push(change); | |
resultIndices[changeId] = results.length - 1; | |
cursor['continue'](); | |
}; | |
}; | |
} | |
function onTxnComplete() { | |
utils.processChanges(opts, dedupResults, last_seq); | |
} | |
}; | |
api._close = function (callback) { | |
if (idb === null) { | |
return callback(errors.NOT_OPEN); | |
} | |
// https://developer.mozilla.org/en-US/docs/IndexedDB/IDBDatabase#close | |
// "Returns immediately and closes the connection in a separate thread..." | |
idb.close(); | |
callback(); | |
}; | |
api._getRevisionTree = function (docId, callback) { | |
var txn = idb.transaction([DOC_STORE], 'readonly'); | |
var req = txn.objectStore(DOC_STORE).get(docId); | |
req.onsuccess = function (event) { | |
var doc = event.target.result; | |
if (!doc) { | |
callback(errors.MISSING_DOC); | |
} else { | |
callback(null, doc.rev_tree); | |
} | |
}; | |
}; | |
// This function removes revisions of document docId | |
// which are listed in revs and sets this document | |
// revision to to rev_tree | |
api._doCompaction = function (docId, rev_tree, revs, callback) { | |
var txn = idb.transaction([DOC_STORE, BY_SEQ_STORE], 'readwrite'); | |
var index = txn.objectStore(DOC_STORE); | |
index.get(docId).onsuccess = function (event) { | |
var metadata = event.target.result; | |
metadata.rev_tree = rev_tree; | |
var count = revs.length; | |
revs.forEach(function (rev) { | |
var index = txn.objectStore(BY_SEQ_STORE).index('_doc_id_rev'); | |
var key = docId + "::" + rev; | |
index.getKey(key).onsuccess = function (e) { | |
var seq = e.target.result; | |
if (!seq) { | |
return; | |
} | |
txn.objectStore(BY_SEQ_STORE)['delete'](seq); | |
count--; | |
if (!count) { | |
if (metadata) { | |
var deleted = utils.isDeleted(metadata); | |
var local = utils.isLocalId(metadata.id); | |
metadata = utils.extend(true, {deletedOrLocal : (deleted || local) ? "1" : "0"}, metadata); | |
} | |
txn.objectStore(DOC_STORE).put(metadata); | |
} | |
}; | |
}); | |
}; | |
txn.oncomplete = function () { | |
utils.call(callback); | |
}; | |
}; | |
} | |
IdbPouch.valid = function () { | |
return global.indexedDB && isModernIdb(); | |
}; | |
IdbPouch.destroy = utils.toPromise(function (name, opts, callback) { | |
if (!('openReqList' in IdbPouch)) { | |
IdbPouch.openReqList = {}; | |
} | |
IdbPouch.Changes.clearListeners(name); | |
//Close open request for "name" database to fix ie delay. | |
if (IdbPouch.openReqList[name] && IdbPouch.openReqList[name].result) { | |
IdbPouch.openReqList[name].result.close(); | |
} | |
var req = global.indexedDB.deleteDatabase(name); | |
req.onsuccess = function () { | |
//Remove open request from the list. | |
if (IdbPouch.openReqList[name]) { | |
IdbPouch.openReqList[name] = null; | |
} | |
callback(); | |
}; | |
req.onerror = idbError(callback); | |
}); | |
IdbPouch.Changes = new utils.Changes(); | |
module.exports = IdbPouch; | |
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{"../deps/errors":8,"../merge":14,"../utils":18}],4:[function(_dereq_,module,exports){ | |
(function (global){ | |
'use strict'; | |
var utils = _dereq_('../utils'); | |
var merge = _dereq_('../merge'); | |
var errors = _dereq_('../deps/errors'); | |
function quote(str) { | |
return "'" + str + "'"; | |
} | |
var openDB = utils.getArguments(function (args) { | |
if (typeof global !== 'undefined') { | |
if (global.navigator && global.navigator.sqlitePlugin && | |
global.navigator.sqlitePlugin.openDatabase) { | |
return navigator.sqlitePlugin.openDatabase | |
.apply(navigator.sqlitePlugin, args); | |
} else if (global.sqlitePlugin && global.sqlitePlugin.openDatabase) { | |
return global.sqlitePlugin.openDatabase | |
.apply(global.sqlitePlugin, args); | |
} else { | |
return global.openDatabase.apply(global, args); | |
} | |
} | |
}); | |
var POUCH_VERSION = 1; | |
var POUCH_SIZE = 5 * 1024 * 1024; | |
var ADAPTER_VERSION = 2; // used to manage migrations | |
// The object stores created for each database | |
// DOC_STORE stores the document meta data, its revision history and state | |
var DOC_STORE = quote('document-store'); | |
// BY_SEQ_STORE stores a particular version of a document, keyed by its | |
// sequence id | |
var BY_SEQ_STORE = quote('by-sequence'); | |
// Where we store attachments | |
var ATTACH_STORE = quote('attach-store'); | |
var META_STORE = quote('metadata-store'); | |
// these indexes cover the ground for most allDocs queries | |
var BY_SEQ_STORE_DELETED_INDEX_SQL = 'CREATE INDEX IF NOT EXISTS \'by-seq-deleted-idx\' ON ' + | |
BY_SEQ_STORE + ' (seq, deleted)'; | |
var DOC_STORE_LOCAL_INDEX_SQL = 'CREATE INDEX IF NOT EXISTS \'doc-store-local-idx\' ON ' + | |
DOC_STORE + ' (local, id)'; | |
var DOC_STORE_WINNINGSEQ_INDEX_SQL = 'CREATE INDEX IF NOT EXISTS \'doc-winningseq-idx\' ON ' + | |
DOC_STORE + ' (winningseq)'; | |
var idRequests = []; | |
var cachedDatabases = {}; | |
function unknownError(callback) { | |
return function (event) { | |
// event may actually be a SQLError object, so report is as such | |
var errorNameMatch = event && event.constructor.toString() | |
.match(/function ([^\(]+)/); | |
var errorName = (errorNameMatch && errorNameMatch[1]) || event.type; | |
var errorReason = event.target || event.message; | |
callback(errors.error(errors.WSQ_ERROR, errorReason, errorName)); | |
}; | |
} | |
function parseHexString(str) { | |
var result = ''; | |
for (var i = 0, len = str.length; i < len; i += 2) { | |
result += String.fromCharCode(parseInt(str.substring(i, i + 2), 16)); | |
} | |
return result; | |
} | |
// Safari is weird, it encodes everything with bonus \u0000 characters after | |
// every character rather than user agent sniff, we test every odd | |
// character for \u0000 | |
function isMangledUnicode(str) { | |
for (var i = 1, len = str.length; i < len; i += 2) { | |
if (str.charAt(i) !== '\u0000') { | |
return false; | |
} | |
} | |
return true; | |
} | |
// unmangle the aforementioned Safari atrocity | |
function unmangleUnicode(str) { | |
var result = ''; | |
for (var i = 0, len = str.length; i < len; i += 2) { | |
result += str.charAt(i); | |
} | |
return result; | |
} | |
// used to deal with utf8 encoding that occurs in most sqlite implementations | |
// partially taken from | |
// http://ecmanaut.blogspot.ca/2006/07/encoding-decoding-utf8-in-javascript.html | |
function decodeUtf8(str) { | |
var result; | |
try { | |
result = decodeURIComponent(window.escape(str)); | |
} catch (err) { | |
// URI error in safari, string is already escaped but still possibly mangled | |
result = str; | |
} | |
return isMangledUnicode(result) ? unmangleUnicode(result) : result; | |
} | |
function WebSqlPouch(opts, callback) { | |
var api = this; | |
var instanceId = null; | |
var name = opts.name; | |
var db = cachedDatabases[name]; | |
if (!db) { | |
cachedDatabases[name] = db = openDB(name, POUCH_VERSION, name, POUCH_SIZE); | |
} | |
if (!db) { | |
return callback(errors.UNKNOWN_ERROR); | |
} | |
function dbCreated() { | |
// note the db name in case the browser upgrades to idb | |
if (utils.hasLocalStorage()) { | |
global.localStorage['_pouch__websqldb_' + name] = true; | |
} | |
callback(null, api); | |
} | |
// In this migration, we added the 'deleted' and 'local' columns to the by-seq and doc store tables. | |
// To preserve existing user data, we re-process all the existing JSON | |
// and add these values. | |
// Called migration2 because it corresponds to adapter version (db_version) #2 | |
function runMigration2(tx) { | |
tx.executeSql(DOC_STORE_WINNINGSEQ_INDEX_SQL); // index used for the join in the allDocs query | |
tx.executeSql('ALTER TABLE ' + BY_SEQ_STORE + ' ADD COLUMN deleted TINYINT(1) DEFAULT 0', [], function () { | |
tx.executeSql(BY_SEQ_STORE_DELETED_INDEX_SQL); | |
tx.executeSql('ALTER TABLE ' + DOC_STORE + ' ADD COLUMN local TINYINT(1) DEFAULT 0', [], function () { | |
tx.executeSql(DOC_STORE_LOCAL_INDEX_SQL); | |
var sql = 'SELECT ' + DOC_STORE + '.winningseq AS seq, ' + DOC_STORE + '.json AS metadata FROM ' + | |
BY_SEQ_STORE + ' JOIN ' + DOC_STORE + ' ON ' + BY_SEQ_STORE + '.seq = ' + | |
DOC_STORE + '.winningseq'; | |
tx.executeSql(sql, [], function (tx, result) { | |
var deleted = []; | |
var local = []; | |
for (var i = 0; i < result.rows.length; i++) { | |
var item = result.rows.item(i); | |
var seq = item.seq; | |
var metadata = JSON.parse(item.metadata); | |
if (utils.isDeleted(metadata)) { | |
deleted.push(seq); | |
} | |
if (utils.isLocalId(metadata.id)) { | |
local.push(metadata.id); | |
} | |
} | |
tx.executeSql('UPDATE ' + DOC_STORE + 'SET local = 1 WHERE id IN (' + local.map(function () { | |
return '?'; | |
}).join(',') + ')', local); | |
tx.executeSql('UPDATE ' + BY_SEQ_STORE + ' SET deleted = 1 WHERE seq IN (' + deleted.map(function () { | |
return '?'; | |
}).join(',') + ')', deleted); | |
}); | |
}); | |
}); | |
} | |
function onGetInstanceId() { | |
while (idRequests.length > 0) { | |
var idCallback = idRequests.pop(); | |
idCallback(null, instanceId); | |
} | |
} | |
function onGetVersion(tx, dbVersion) { | |
if (dbVersion === 0) { | |
// initial schema | |
var meta = 'CREATE TABLE IF NOT EXISTS ' + META_STORE + | |
' (update_seq, dbid, db_version INTEGER)'; | |
var attach = 'CREATE TABLE IF NOT EXISTS ' + ATTACH_STORE + | |
' (digest, json, body BLOB)'; | |
var doc = 'CREATE TABLE IF NOT EXISTS ' + DOC_STORE + | |
' (id unique, seq, json, winningseq, local TINYINT(1))'; | |
var seq = 'CREATE TABLE IF NOT EXISTS ' + BY_SEQ_STORE + | |
' (seq INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, doc_id_rev UNIQUE, json, deleted TINYINT(1))'; | |
// creates | |
tx.executeSql(attach); | |
tx.executeSql(doc, [], function () { | |
tx.executeSql(DOC_STORE_WINNINGSEQ_INDEX_SQL); | |
tx.executeSql(DOC_STORE_LOCAL_INDEX_SQL); | |
}); | |
tx.executeSql(seq, [], function () { | |
tx.executeSql(BY_SEQ_STORE_DELETED_INDEX_SQL); | |
}); | |
tx.executeSql(meta, [], function () { | |
// mark the update_seq, db version, and new dbid | |
var initSeq = 'INSERT INTO ' + META_STORE + ' (update_seq, db_version, dbid) VALUES (?, ?, ?)'; | |
instanceId = utils.uuid(); | |
tx.executeSql(initSeq, [0, ADAPTER_VERSION, instanceId]); | |
onGetInstanceId(); | |
}); | |
} else { // version > 0 | |
if (dbVersion === 1) { | |
runMigration2(tx); | |
// mark the db version within this transaction | |
tx.executeSql('UPDATE ' + META_STORE + ' SET db_version = ' + ADAPTER_VERSION); | |
} // in the future, add more migrations here | |
// notify db.id() callers | |
tx.executeSql('SELECT dbid FROM ' + META_STORE, [], function (tx, result) { | |
instanceId = result.rows.item(0).dbid; | |
onGetInstanceId(); | |
}); | |
} | |
} | |
function setup() { | |
db.transaction(function (tx) { | |
// first get the version | |
tx.executeSql('SELECT sql FROM sqlite_master WHERE tbl_name = ' + META_STORE, [], function (tx, result) { | |
if (!result.rows.length) { | |
// database hasn't even been created yet (version 0) | |
onGetVersion(tx, 0); | |
} else if (!/db_version/.test(result.rows.item(0).sql)) { | |
// table was created, but without the new db_version column, so add it. | |
tx.executeSql('ALTER TABLE ' + META_STORE + ' ADD COLUMN db_version INTEGER', [], function () { | |
onGetVersion(tx, 1); // before version 2, this column didn't even exist | |
}); | |
} else { // column exists, we can safely get it | |
tx.executeSql('SELECT db_version FROM ' + META_STORE, [], function (tx, result) { | |
var dbVersion = result.rows.item(0).db_version; | |
onGetVersion(tx, dbVersion); | |
}); | |
} | |
}); | |
}, unknownError(callback), dbCreated); | |
} | |
if (utils.isCordova() && typeof global !== 'undefined') { | |
//to wait until custom api is made in pouch.adapters before doing setup | |
global.addEventListener(name + '_pouch', function cordova_init() { | |
global.removeEventListener(name + '_pouch', cordova_init, false); | |
setup(); | |
}, false); | |
} else { | |
setup(); | |
} | |
api.type = function () { | |
return 'websql'; | |
}; | |
api._id = utils.toPromise(function (callback) { | |
callback(null, instanceId); | |
}); | |
api._info = function (callback) { | |
db.transaction(function (tx) { | |
var sql = 'SELECT COUNT(id) AS count FROM ' + DOC_STORE; | |
tx.executeSql(sql, [], function (tx, result) { | |
var doc_count = result.rows.item(0).count; | |
var updateseq = 'SELECT update_seq FROM ' + META_STORE; | |
tx.executeSql(updateseq, [], function (tx, result) { | |
var update_seq = result.rows.item(0).update_seq; | |
callback(null, { | |
db_name: name, | |
doc_count: doc_count, | |
update_seq: update_seq | |
}); | |
}); | |
}); | |
}); | |
}; | |
api._bulkDocs = function (req, opts, callback) { | |
var newEdits = opts.new_edits; | |
var userDocs = req.docs; | |
var docsWritten = 0; | |
// Parse the docs, give them a sequence number for the result | |
var docInfos = userDocs.map(function (doc, i) { | |
var newDoc = utils.parseDoc(doc, newEdits); | |
newDoc._bulk_seq = i; | |
return newDoc; | |
}); | |
var docInfoErrors = docInfos.filter(function (docInfo) { | |
return docInfo.error; | |
}); | |
if (docInfoErrors.length) { | |
return callback(docInfoErrors[0]); | |
} | |
var tx; | |
var results = []; | |
var fetchedDocs = {}; | |
function sortByBulkSeq(a, b) { | |
return a._bulk_seq - b._bulk_seq; | |
} | |
function complete(event) { | |
var aresults = []; | |
results.sort(sortByBulkSeq); | |
results.forEach(function (result) { | |
delete result._bulk_seq; | |
if (result.error) { | |
aresults.push(result); | |
return; | |
} | |
var metadata = result.metadata; | |
var rev = merge.winningRev(metadata); | |
aresults.push({ | |
ok: true, | |
id: metadata.id, | |
rev: rev | |
}); | |
if (utils.isLocalId(metadata.id)) { | |
return; | |
} | |
docsWritten++; | |
WebSqlPouch.Changes.notify(name); | |
WebSqlPouch.Changes.notifyLocalWindows(name); | |
}); | |
var updateseq = 'SELECT update_seq FROM ' + META_STORE; | |
tx.executeSql(updateseq, [], function (tx, result) { | |
var update_seq = result.rows.item(0).update_seq + docsWritten; | |
var sql = 'UPDATE ' + META_STORE + ' SET update_seq=?'; | |
tx.executeSql(sql, [update_seq], function () { | |
callback(null, aresults); | |
}); | |
}); | |
} | |
function preprocessAttachment(att, finish) { | |
if (att.stub) { | |
return finish(); | |
} | |
if (typeof att.data === 'string') { | |
try { | |
att.data = atob(att.data); | |
} catch (e) { | |
var err = errors.error(errors.BAD_ARG, | |
"Attachments need to be base64 encoded"); | |
return callback(err); | |
} | |
var data = utils.fixBinary(att.data); | |
att.data = utils.createBlob([data], {type: att.content_type}); | |
} | |
var reader = new FileReader(); | |
reader.onloadend = function (e) { | |
var binary = utils.arrayBufferToBinaryString(this.result); | |
att.data = binary; | |
att.digest = 'md5-' + utils.Crypto.MD5(binary); | |
finish(); | |
}; | |
reader.readAsArrayBuffer(att.data); | |
} | |
function preprocessAttachments(callback) { | |
if (!docInfos.length) { | |
return callback(); | |
} | |
var docv = 0; | |
docInfos.forEach(function (docInfo) { | |
var attachments = docInfo.data && docInfo.data._attachments ? | |
Object.keys(docInfo.data._attachments) : []; | |
var recv = 0; | |
if (!attachments.length) { | |
return done(); | |
} | |
function processedAttachment() { | |
recv++; | |
if (recv === attachments.length) { | |
done(); | |
} | |
} | |
for (var key in docInfo.data._attachments) { | |
if (docInfo.data._attachments.hasOwnProperty(key)) { | |
preprocessAttachment(docInfo.data._attachments[key], processedAttachment); | |
} | |
} | |
}); | |
function done() { | |
docv++; | |
if (docInfos.length === docv) { | |
callback(); | |
} | |
} | |
} | |
function writeDoc(docInfo, callback, isUpdate) { | |
function finish() { | |
var data = docInfo.data; | |
var doc_id_rev = data._id + "::" + data._rev; | |
var deleted = utils.isDeleted(docInfo.metadata, docInfo.metadata.rev) ? 1 : 0; | |
var fetchSql = 'SELECT * FROM ' + BY_SEQ_STORE + ' WHERE doc_id_rev=?;'; | |
tx.executeSql(fetchSql, [doc_id_rev], function (err, res) { | |
var sql, sqlArgs; | |
if (res.rows.length) { | |
sql = 'UPDATE ' + BY_SEQ_STORE + | |
' SET json=?, deleted=? WHERE doc_id_rev=?;'; | |
sqlArgs = [JSON.stringify(data), deleted, doc_id_rev]; | |
tx.executeSql(sql, sqlArgs, function (tx) { | |
dataWritten(tx, res.rows.item(0).seq); | |
}); | |
} else { | |
sql = 'INSERT INTO ' + BY_SEQ_STORE + | |
' (doc_id_rev, json, deleted) VALUES (?, ?, ?);'; | |
sqlArgs = [doc_id_rev, JSON.stringify(data), deleted]; | |
tx.executeSql(sql, sqlArgs, function (tx, result) { | |
dataWritten(tx, result.insertId); | |
}); | |
} | |
}); | |
} | |
function collectResults(attachmentErr) { | |
if (!err) { | |
if (attachmentErr) { | |
err = attachmentErr; | |
callback(err); | |
} else if (recv === attachments.length) { | |
finish(); | |
} | |
} | |
} | |
var err = null; | |
var recv = 0; | |
docInfo.data._id = docInfo.metadata.id; | |
docInfo.data._rev = docInfo.metadata.rev; | |
if (utils.isDeleted(docInfo.metadata, docInfo.metadata.rev)) { | |
docInfo.data._deleted = true; | |
} | |
var attachments = docInfo.data._attachments ? | |
Object.keys(docInfo.data._attachments) : []; | |
function attachmentSaved(err) { | |
recv++; | |
collectResults(err); | |
} | |
for (var key in docInfo.data._attachments) { | |
if (!docInfo.data._attachments[key].stub) { | |
var data = docInfo.data._attachments[key].data; | |
delete docInfo.data._attachments[key].data; | |
var digest = docInfo.data._attachments[key].digest; | |
saveAttachment(docInfo, digest, data, attachmentSaved); | |
} else { | |
recv++; | |
collectResults(); | |
} | |
} | |
if (!attachments.length) { | |
finish(); | |
} | |
function dataWritten(tx, seq) { | |
docInfo.metadata.seq = seq; | |
delete docInfo.metadata.rev; | |
var mainRev = merge.winningRev(docInfo.metadata); | |
var sql = isUpdate ? | |
'UPDATE ' + DOC_STORE + ' SET seq=?, json=?, winningseq=(SELECT seq FROM ' + | |
BY_SEQ_STORE + ' WHERE doc_id_rev=?) WHERE id=?' : | |
'INSERT INTO ' + DOC_STORE + ' (id, seq, winningseq, json, local) VALUES (?, ?, ?, ?, ?);'; | |
var metadataStr = JSON.stringify(docInfo.metadata); | |
var key = docInfo.metadata.id + "::" + mainRev; | |
var local = utils.isLocalId(docInfo.metadata.id) ? 1 : 0; | |
var params = isUpdate ? | |
[seq, metadataStr, key, docInfo.metadata.id] : | |
[docInfo.metadata.id, seq, seq, metadataStr, local]; | |
tx.executeSql(sql, params, function (tx, result) { | |
results.push(docInfo); | |
callback(); | |
}); | |
} | |
} | |
function updateDoc(oldDoc, docInfo) { | |
var merged = merge.merge(oldDoc.rev_tree, docInfo.metadata.rev_tree[0], 1000); | |
var inConflict = (utils.isDeleted(oldDoc) && | |
utils.isDeleted(docInfo.metadata)) || | |
(!utils.isDeleted(oldDoc) && | |
newEdits && merged.conflicts !== 'new_leaf'); | |
if (inConflict) { | |
results.push(makeErr(errors.REV_CONFLICT, docInfo._bulk_seq)); | |
return processDocs(); | |
} | |
docInfo.metadata.rev_tree = merged.tree; | |
writeDoc(docInfo, processDocs, true); | |
} | |
function insertDoc(docInfo) { | |
// Cant insert new deleted documents | |
if ('was_delete' in opts && utils.isDeleted(docInfo.metadata)) { | |
results.push(errors.MISSING_DOC); | |
return processDocs(); | |
} | |
writeDoc(docInfo, processDocs, false); | |
} | |
function processDocs() { | |
if (!docInfos.length) { | |
return complete(); | |
} | |
var currentDoc = docInfos.shift(); | |
var id = currentDoc.metadata.id; | |
if (id in fetchedDocs) { | |
updateDoc(fetchedDocs[id], currentDoc); | |
} else { | |
// if we have newEdits=false then we can update the same | |
// document twice in a single bulk docs call | |
fetchedDocs[id] = currentDoc.metadata; | |
insertDoc(currentDoc); | |
} | |
} | |
// Insert sequence number into the error so we can sort later | |
function makeErr(err, seq) { | |
err._bulk_seq = seq; | |
return err; | |
} | |
function saveAttachment(docInfo, digest, data, callback) { | |
var ref = [docInfo.metadata.id, docInfo.metadata.rev].join('@'); | |
var newAtt = {digest: digest}; | |
var sql = 'SELECT digest, json FROM ' + ATTACH_STORE + ' WHERE digest=?'; | |
tx.executeSql(sql, [digest], function (tx, result) { | |
if (!result.rows.length) { | |
newAtt.refs = {}; | |
newAtt.refs[ref] = true; | |
sql = 'INSERT INTO ' + ATTACH_STORE + '(digest, json, body) VALUES (?, ?, ?)'; | |
tx.executeSql(sql, [digest, JSON.stringify(newAtt), data], function () { | |
callback(); | |
}); | |
} else { | |
newAtt.refs = JSON.parse(result.rows.item(0).json).refs; | |
sql = 'UPDATE ' + ATTACH_STORE + ' SET json=?, body=? WHERE digest=?'; | |
tx.executeSql(sql, [JSON.stringify(newAtt), data, digest], function () { | |
callback(); | |
}); | |
} | |
}); | |
} | |
function metadataFetched(tx, results) { | |
for (var j = 0; j < results.rows.length; j++) { | |
var row = results.rows.item(j); | |
fetchedDocs[row.id] = JSON.parse(row.json); | |
} | |
processDocs(); | |
} | |
preprocessAttachments(function () { | |
db.transaction(function (txn) { | |
tx = txn; | |
var sql = 'SELECT * FROM ' + DOC_STORE + ' WHERE id IN ' + | |
'(' + docInfos.map(function () {return '?'; }).join(',') + ')'; | |
var queryArgs = docInfos.map(function (d) { return d.metadata.id; }); | |
tx.executeSql(sql, queryArgs, metadataFetched); | |
}, unknownError(callback)); | |
}); | |
}; | |
api._get = function (id, opts, callback) { | |
opts = utils.extend(true, {}, opts); | |
var doc; | |
var metadata; | |
var err; | |
if (!opts.ctx) { | |
db.transaction(function (txn) { | |
opts.ctx = txn; | |
api._get(id, opts, callback); | |
}); | |
return; | |
} | |
var tx = opts.ctx; | |
function finish() { | |
callback(err, {doc: doc, metadata: metadata, ctx: tx}); | |
} | |
var sql = 'SELECT * FROM ' + DOC_STORE + ' WHERE id=?'; | |
tx.executeSql(sql, [id], function (a, results) { | |
if (!results.rows.length) { | |
err = errors.MISSING_DOC; | |
return finish(); | |
} | |
metadata = JSON.parse(results.rows.item(0).json); | |
if (utils.isDeleted(metadata) && !opts.rev) { | |
err = errors.error(errors.MISSING_DOC, "deleted"); | |
return finish(); | |
} | |
var rev = merge.winningRev(metadata); | |
var key = opts.rev ? opts.rev : rev; | |
key = metadata.id + '::' + key; | |
var sql = 'SELECT * FROM ' + BY_SEQ_STORE + ' WHERE doc_id_rev=?'; | |
tx.executeSql(sql, [key], function (tx, results) { | |
if (!results.rows.length) { | |
err = errors.MISSING_DOC; | |
return finish(); | |
} | |
doc = JSON.parse(results.rows.item(0).json); | |
finish(); | |
}); | |
}); | |
}; | |
api._allDocs = function (opts, callback) { | |
var results = []; | |
var resultsMap = {}; | |
var totalRows; | |
var from = BY_SEQ_STORE + ' JOIN ' + DOC_STORE + ' ON ' + BY_SEQ_STORE + '.seq = ' + | |
DOC_STORE + '.winningseq'; | |
var start = 'startkey' in opts ? opts.startkey : false; | |
var end = 'endkey' in opts ? opts.endkey : false; | |
var key = 'key' in opts ? opts.key : false; | |
var descending = 'descending' in opts ? opts.descending : false; | |
var keys = 'keys' in opts ? opts.keys : false; | |
var limit = 'limit' in opts ? opts.limit : false; | |
var offset = 'skip' in opts ? opts.skip : false; | |
var sqlArgs = []; | |
var criteria = [DOC_STORE + '.local = 0']; | |
if (key !== false) { | |
criteria.push(DOC_STORE + '.id = ?'); | |
sqlArgs.push(key); | |
} else if (keys !== false) { | |
criteria.push(DOC_STORE + '.id in (' + keys.map(function () { | |
return '?'; | |
}).join(',') + ')'); | |
sqlArgs = sqlArgs.concat(keys); | |
} else if (start !== false || end !== false) { | |
if (start !== false) { | |
criteria.push(DOC_STORE + '.id ' + (descending ? '<=' : '>=') + ' ?'); | |
sqlArgs.push(start); | |
} | |
if (end !== false) { | |
criteria.push(DOC_STORE + '.id ' + (descending ? '>=' : '<=') + ' ?'); | |
sqlArgs.push(end); | |
} | |
if (key !== false) { | |
criteria.push(DOC_STORE + '.id = ?'); | |
sqlArgs.push(key); | |
} | |
} | |
if (keys === false) { | |
// report deleted if keys are specified | |
criteria.push(BY_SEQ_STORE + '.deleted = 0'); | |
} | |
db.transaction(function (tx) { | |
// first count up the total rows | |
var sql = 'SELECT COUNT(' + DOC_STORE + '.id) AS \'num\' FROM ' + | |
from + ' WHERE ' + BY_SEQ_STORE + '.deleted = 0 AND ' + | |
// local docs are e.g. '_local_foo' | |
DOC_STORE + '.local = 0'; | |
tx.executeSql(sql, [], function (tx, result) { | |
totalRows = result.rows.item(0).num; | |
if (limit === 0) { | |
return; | |
} | |
// then actually fetch the documents | |
var sql = 'SELECT ' + DOC_STORE + '.id, ' + BY_SEQ_STORE + '.seq, ' + | |
BY_SEQ_STORE + '.json AS data, ' + DOC_STORE + '.json AS metadata FROM ' + from; | |
if (criteria.length) { | |
sql += ' WHERE ' + criteria.join(' AND '); | |
} | |
sql += ' ORDER BY ' + DOC_STORE + '.id ' + (descending ? 'DESC' : 'ASC'); | |
if (limit !== false) { | |
sql += ' LIMIT ' + limit; | |
} | |
if (offset !== false && offset > 0) { | |
if (limit === false) { | |
// sqlite requires limit with offset, -1 acts as infinity here | |
sql += ' LIMIT -1'; | |
} | |
sql += ' OFFSET ' + offset; | |
} | |
tx.executeSql(sql, sqlArgs, function (tx, result) { | |
for (var i = 0, l = result.rows.length; i < l; i++) { | |
var doc = result.rows.item(i); | |
var metadata = JSON.parse(doc.metadata); | |
var data = JSON.parse(doc.data); | |
doc = { | |
id: metadata.id, | |
key: metadata.id, | |
value: {rev: merge.winningRev(metadata)} | |
}; | |
if (opts.include_docs) { | |
doc.doc = data; | |
doc.doc._rev = merge.winningRev(metadata); | |
if (opts.conflicts) { | |
doc.doc._conflicts = merge.collectConflicts(metadata); | |
} | |
for (var att in doc.doc._attachments) { | |
if (doc.doc._attachments.hasOwnProperty(att)) { | |
doc.doc._attachments[att].stub = true; | |
} | |
} | |
} | |
if ('keys' in opts) { | |
if (opts.keys.indexOf(metadata.id) > -1) { | |
if (utils.isDeleted(metadata)) { | |
doc.value.deleted = true; | |
doc.doc = null; | |
} | |
resultsMap[doc.id] = doc; | |
} | |
} else { | |
results.push(doc); | |
} | |
} | |
}); | |
}); | |
}, unknownError(callback), function () { | |
if (limit !== 0 && 'keys' in opts) { | |
opts.keys.forEach(function (key) { | |
if (key in resultsMap) { | |
results.push(resultsMap[key]); | |
} else { | |
results.push({"key": key, "error": "not_found"}); | |
} | |
}); | |
if (opts.descending) { | |
results.reverse(); | |
} | |
} | |
callback(null, { | |
total_rows: totalRows, | |
offset: opts.skip, | |
rows: results | |
}); | |
}); | |
}; | |
api._changes = function idb_changes(opts) { | |
opts = utils.extend(true, {}, opts); | |
if (opts.continuous) { | |
var id = name + ':' + utils.uuid(); | |
WebSqlPouch.Changes.addListener(name, id, api, opts); | |
WebSqlPouch.Changes.notify(name); | |
return { | |
cancel: function () { | |
WebSqlPouch.Changes.removeListener(name, id); | |
} | |
}; | |
} | |
var descending = opts.descending; | |
// Ignore the `since` parameter when `descending` is true | |
opts.since = opts.since && !descending ? opts.since : 0; | |
var results = []; | |
function fetchChanges() { | |
var sql = 'SELECT ' + DOC_STORE + '.id, ' + BY_SEQ_STORE + '.seq, ' + | |
BY_SEQ_STORE + '.json AS data, ' + DOC_STORE + '.json AS metadata FROM ' + | |
BY_SEQ_STORE + ' JOIN ' + DOC_STORE + ' ON ' + BY_SEQ_STORE + '.seq = ' + | |
DOC_STORE + '.winningseq WHERE ' + DOC_STORE + '.seq > ' + opts.since + | |
' ORDER BY ' + DOC_STORE + '.seq ' + (descending ? 'DESC' : 'ASC'); | |
db.transaction(function (tx) { | |
tx.executeSql(sql, [], function (tx, result) { | |
var last_seq = 0; | |
for (var i = 0, l = result.rows.length; i < l; i++) { | |
var res = result.rows.item(i); | |
var metadata = JSON.parse(res.metadata); | |
if (!utils.isLocalId(metadata.id)) { | |
if (last_seq < res.seq) { | |
last_seq = res.seq; | |
} | |
var doc = JSON.parse(res.data); | |
var change = opts.processChange(doc, metadata, opts); | |
change.seq = res.seq; | |
results.push(change); | |
} | |
} | |
utils.processChanges(opts, results, last_seq); | |
}); | |
}); | |
} | |
fetchChanges(); | |
}; | |
api._close = function (callback) { | |
//WebSQL databases do not need to be closed | |
callback(); | |
}; | |
api._getAttachment = function (attachment, opts, callback) { | |
var res; | |
var tx = opts.ctx; | |
var digest = attachment.digest; | |
var type = attachment.content_type; | |
var sql = 'SELECT hex(body) as body FROM ' + ATTACH_STORE + ' WHERE digest=?'; | |
tx.executeSql(sql, [digest], function (tx, result) { | |
// TODO: sqlite normally stores data as utf8, so even the hex() function "encodes" the binary | |
// data in utf8 before returning it, and yet hex() is the only way to get the full data. so we do this. | |
var data = decodeUtf8(parseHexString(result.rows.item(0).body)); | |
if (opts.encode) { | |
res = btoa(data); | |
} else { | |
data = utils.fixBinary(data); | |
res = utils.createBlob([data], {type: type}); | |
} | |
callback(null, res); | |
}); | |
}; | |
api._getRevisionTree = function (docId, callback) { | |
db.transaction(function (tx) { | |
var sql = 'SELECT json AS metadata FROM ' + DOC_STORE + ' WHERE id = ?'; | |
tx.executeSql(sql, [docId], function (tx, result) { | |
if (!result.rows.length) { | |
callback(errors.MISSING_DOC); | |
} else { | |
var data = JSON.parse(result.rows.item(0).metadata); | |
callback(null, data.rev_tree); | |
} | |
}); | |
}); | |
}; | |
api._doCompaction = function (docId, rev_tree, revs, callback) { | |
db.transaction(function (tx) { | |
var sql = 'SELECT json AS metadata FROM ' + DOC_STORE + ' WHERE id = ?'; | |
tx.executeSql(sql, [docId], function (tx, result) { | |
if (!result.rows.length) { | |
return utils.call(callback); | |
} | |
var metadata = JSON.parse(result.rows.item(0).metadata); | |
metadata.rev_tree = rev_tree; | |
var sql = 'DELETE FROM ' + BY_SEQ_STORE + ' WHERE doc_id_rev IN (' + | |
revs.map(function (rev) {return quote(docId + '::' + rev); }).join(',') + ')'; | |
tx.executeSql(sql, [], function (tx, result) { | |
var sql = 'UPDATE ' + DOC_STORE + ' SET json = ? WHERE id = ?'; | |
tx.executeSql(sql, [JSON.stringify(metadata), docId], function (tx, result) { | |
callback(); | |
}); | |
}); | |
}); | |
}); | |
}; | |
} | |
WebSqlPouch.valid = function () { | |
if (typeof global !== 'undefined') { | |
if (global.navigator && global.navigator.sqlitePlugin && global.navigator.sqlitePlugin.openDatabase) { | |
return true; | |
} else if (global.sqlitePlugin && global.sqlitePlugin.openDatabase) { | |
return true; | |
} else if (global.openDatabase) { | |
return true; | |
} | |
} | |
return false; | |
}; | |
WebSqlPouch.destroy = utils.toPromise(function (name, opts, callback) { | |
var db = openDB(name, POUCH_VERSION, name, POUCH_SIZE); | |
db.transaction(function (tx) { | |
tx.executeSql('DROP TABLE IF EXISTS ' + DOC_STORE, []); | |
tx.executeSql('DROP TABLE IF EXISTS ' + BY_SEQ_STORE, []); | |
tx.executeSql('DROP TABLE IF EXISTS ' + ATTACH_STORE, []); | |
tx.executeSql('DROP TABLE IF EXISTS ' + META_STORE, []); | |
}, unknownError(callback), function () { | |
if (utils.hasLocalStorage()) { | |
delete global.localStorage['_pouch__websqldb_' + name]; | |
} | |
callback(); | |
}); | |
}); | |
WebSqlPouch.Changes = new utils.Changes(); | |
module.exports = WebSqlPouch; | |
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{"../deps/errors":8,"../merge":14,"../utils":18}],5:[function(_dereq_,module,exports){ | |
(function (global){ | |
/*globals cordova */ | |
"use strict"; | |
var Promise = typeof global.Promise === 'function' ? | |
global.Promise : _dereq_('bluebird'); | |
var Adapter = _dereq_('./adapter'); | |
var utils = _dereq_('./utils'); | |
var TaskQueue = _dereq_('./taskqueue'); | |
function defaultCallback(err) { | |
if (err && global.debug) { | |
console.error(err); | |
} | |
} | |
utils.inherits(PouchDB, Adapter); | |
function PouchDB(name, opts, callback) { | |
if (!(this instanceof PouchDB)) { | |
return new PouchDB(name, opts, callback); | |
} | |
var self = this; | |
if (typeof opts === 'function' || typeof opts === 'undefined') { | |
callback = opts; | |
opts = {}; | |
} | |
if (typeof name === 'object') { | |
opts = name; | |
name = undefined; | |
} | |
if (typeof callback === 'undefined') { | |
callback = defaultCallback; | |
} | |
opts = opts || {}; | |
var oldCB = callback; | |
self.auto_compaction = opts.auto_compaction; | |
self.prefix = PouchDB.prefix; | |
Adapter.call(self); | |
self.taskqueue = new TaskQueue(); | |
var promise = new Promise(function (fulfill, reject) { | |
callback = function (err, resp) { | |
if (err) { | |
return reject(err); | |
} | |
delete resp.then; | |
fulfill(resp); | |
}; | |
opts = utils.extend(true, {}, opts); | |
var originalName = opts.name || name; | |
var backend, error; | |
(function () { | |
try { | |
if (typeof originalName !== 'string') { | |
error = new Error('Missing/invalid DB name'); | |
error.code = 400; | |
throw error; | |
} | |
backend = PouchDB.parseAdapter(originalName, opts); | |
opts.originalName = originalName; | |
opts.name = backend.name; | |
opts.adapter = opts.adapter || backend.adapter; | |
if (!PouchDB.adapters[opts.adapter]) { | |
error = new Error('Adapter is missing'); | |
error.code = 404; | |
throw error; | |
} | |
if (!PouchDB.adapters[opts.adapter].valid()) { | |
error = new Error('Invalid Adapter'); | |
error.code = 404; | |
throw error; | |
} | |
} catch (err) { | |
self.taskqueue.fail(err); | |
self.changes = utils.toPromise(function (opts) { | |
if (opts.complete) { | |
opts.complete(err); | |
} | |
}); | |
} | |
}()); | |
if (error) { | |
return reject(error); // constructor error, see above | |
} | |
self.adapter = opts.adapter; | |
// needs access to PouchDB; | |
self.replicate = function (src, target, opts) { | |
return utils.cancellableFun(function (api, _opts, promise) { | |
var replicate = PouchDB.replicate(src, target, opts); | |
promise.cancel = replicate.cancel; | |
}, self, opts); | |
}; | |
self.replicate.from = function (url, opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
return PouchDB.replicate(url, self, opts, callback); | |
}; | |
self.replicate.to = function (url, opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
return PouchDB.replicate(self, url, opts, callback); | |
}; | |
self.replicate.sync = function (dbName, opts, callback) { | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
return utils.cancellableFun(function (api, _opts, promise) { | |
var sync = PouchDB.sync(self, dbName, opts, callback); | |
promise.cancel = sync.cancel; | |
}, self, opts); | |
}; | |
self.destroy = utils.adapterFun('destroy', function (callback) { | |
var self = this; | |
self.info(function (err, info) { | |
if (err) { | |
return callback(err); | |
} | |
PouchDB.destroy(info.db_name, callback); | |
}); | |
}); | |
PouchDB.adapters[opts.adapter].call(self, opts, function (err, db) { | |
if (err) { | |
if (callback) { | |
self.taskqueue.fail(err); | |
callback(err); | |
} | |
return; | |
} | |
function destructionListener(event) { | |
if (event === 'destroyed') { | |
self.emit('destroyed'); | |
PouchDB.removeListener(opts.name, destructionListener); | |
} | |
} | |
PouchDB.on(opts.name, destructionListener); | |
self.emit('created', self); | |
PouchDB.emit('created', opts.originalName); | |
self.taskqueue.ready(self); | |
callback(null, self); | |
}); | |
if (opts.skipSetup) { | |
self.taskqueue.ready(self); | |
} | |
if (utils.isCordova()) { | |
//to inform websql adapter that we can use api | |
cordova.fireWindowEvent(opts.name + "_pouch", {}); | |
} | |
}); | |
promise.then(function (resp) { | |
oldCB(null, resp); | |
}, oldCB); | |
self.then = promise.then.bind(promise); | |
//prevent deoptimizing | |
(function () { | |
try { | |
self['catch'] = promise['catch'].bind(promise); | |
} catch (e) {} | |
}()); | |
} | |
module.exports = PouchDB; | |
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{"./adapter":1,"./taskqueue":17,"./utils":18,"bluebird":28}],6:[function(_dereq_,module,exports){ | |
(function (process){ | |
"use strict"; | |
var request = _dereq_('request'); | |
var extend = _dereq_('./extend.js'); | |
var createBlob = _dereq_('./blob.js'); | |
var errors = _dereq_('./errors'); | |
var uuid = _dereq_('../deps/uuid'); | |
var utils = _dereq_("../utils"); | |
function ajax(options, adapterCallback) { | |
var requestCompleted = false; | |
var callback = utils.getArguments(function (args) { | |
if (requestCompleted) { | |
return; | |
} | |
adapterCallback.apply(this, args); | |
requestCompleted = true; | |
}); | |
if (typeof options === "function") { | |
callback = options; | |
options = {}; | |
} | |
options = extend(true, {}, options); | |
var defaultOptions = { | |
method : "GET", | |
headers: {}, | |
json: true, | |
processData: true, | |
timeout: 10000, | |
cache: false | |
}; | |
options = extend(true, defaultOptions, options); | |
// cache-buster, specifically designed to work around IE's aggressive caching | |
// see http://www.dashbay.com/2011/05/internet-explorer-caches-ajax/ | |
if (options.method === 'GET' && !options.cache) { | |
var hasArgs = options.url.indexOf('?') !== -1; | |
options.url += (hasArgs ? '&' : '?') + '_nonce=' + uuid(16); | |
} | |
function onSuccess(obj, resp, cb) { | |
if (!options.binary && !options.json && options.processData && | |
typeof obj !== 'string') { | |
obj = JSON.stringify(obj); | |
} else if (!options.binary && options.json && typeof obj === 'string') { | |
try { | |
obj = JSON.parse(obj); | |
} catch (e) { | |
// Probably a malformed JSON from server | |
return cb(e); | |
} | |
} | |
if (Array.isArray(obj)) { | |
obj = obj.map(function (v) { | |
var obj; | |
if (v.ok) { | |
return v; | |
} else if (v.error && v.error === 'conflict') { | |
obj = errors.REV_CONFLICT; | |
obj.id = v.id; | |
return obj; | |
} else if (v.error && v.error === 'forbidden') { | |
obj = errors.FORBIDDEN; | |
obj.id = v.id; | |
obj.reason = v.reason; | |
return obj; | |
} else if (v.missing) { | |
obj = errors.MISSING_DOC; | |
obj.missing = v.missing; | |
return obj; | |
} else { | |
return v; | |
} | |
}); | |
} | |
cb(null, obj, resp); | |
} | |
function onError(err, cb) { | |
var errParsed, errObj, errType, key; | |
try { | |
errParsed = JSON.parse(err.responseText); | |
//would prefer not to have a try/catch clause | |
for (key in errors) { | |
if (errors.hasOwnProperty(key) && | |
errors[key].name === errParsed.error) { | |
errType = errors[key]; | |
break; | |
} | |
} | |
if (!errType) { | |
errType = errors.UNKNOWN_ERROR; | |
if (err.status) { | |
errType.status = err.status; | |
} | |
if (err.statusText) { | |
err.name = err.statusText; | |
} | |
} | |
errObj = errors.error(errType, errParsed.reason); | |
} catch (e) { | |
for (var key in errors) { | |
if (errors.hasOwnProperty(key) && errors[key].status === err.status) { | |
errType = errors[key]; | |
break; | |
} | |
} | |
if (!errType) { | |
errType = errors.UNKNOWN_ERROR; | |
if (err.status) { | |
errType.status = err.status; | |
} | |
if (err.statusText) { | |
err.name = err.statusText; | |
} | |
} | |
errObj = errors.error(errType); | |
} | |
cb(errObj); | |
} | |
if (process.browser) { | |
var timer; | |
var xhr; | |
if (options.xhr) { | |
xhr = new options.xhr(); | |
} else { | |
xhr = new XMLHttpRequest(); | |
} | |
xhr.open(options.method, options.url); | |
xhr.withCredentials = true; | |
if (options.json) { | |
options.headers.Accept = 'application/json'; | |
options.headers['Content-Type'] = options.headers['Content-Type'] || | |
'application/json'; | |
if (options.body && | |
options.processData && | |
typeof options.body !== "string") { | |
options.body = JSON.stringify(options.body); | |
} | |
} | |
if (options.binary) { | |
xhr.responseType = 'arraybuffer'; | |
} | |
var createCookie = function (name, value, days) { | |
var expires = ""; | |
if (days) { | |
var date = new Date(); | |
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); | |
expires = "; expires=" + date.toGMTString(); | |
} | |
document.cookie = name + "=" + value + expires + "; path=/"; | |
}; | |
for (var key in options.headers) { | |
if (key === 'Cookie') { | |
var cookie = options.headers[key].split('='); | |
createCookie(cookie[0], cookie[1], 10); | |
} else { | |
xhr.setRequestHeader(key, options.headers[key]); | |
} | |
} | |
if (!("body" in options)) { | |
options.body = null; | |
} | |
var abortReq = function () { | |
if (requestCompleted) { | |
return; | |
} | |
xhr.abort(); | |
onError(xhr, callback); | |
}; | |
xhr.onreadystatechange = function () { | |
if (xhr.readyState !== 4 || requestCompleted) { | |
return; | |
} | |
clearTimeout(timer); | |
if (xhr.status >= 200 && xhr.status < 300) { | |
var data; | |
if (options.binary) { | |
data = createBlob([xhr.response || ''], { | |
type: xhr.getResponseHeader('Content-Type') | |
}); | |
} else { | |
data = xhr.responseText; | |
} | |
onSuccess(data, xhr, callback); | |
} else { | |
onError(xhr, callback); | |
} | |
}; | |
if (options.timeout > 0) { | |
timer = setTimeout(abortReq, options.timeout); | |
xhr.onprogress = function () { | |
clearTimeout(timer); | |
timer = setTimeout(abortReq, options.timeout); | |
}; | |
if (xhr.upload) { // does not exist in ie9 | |
xhr.upload.onprogress = xhr.onprogress; | |
} | |
} | |
xhr.send(options.body); | |
return {abort: abortReq}; | |
} else { | |
if (options.json) { | |
if (!options.binary) { | |
options.headers.Accept = 'application/json'; | |
} | |
options.headers['Content-Type'] = options.headers['Content-Type'] || | |
'application/json'; | |
} | |
if (options.binary) { | |
options.encoding = null; | |
options.json = false; | |
} | |
if (!options.processData) { | |
options.json = false; | |
} | |
return request(options, function (err, response, body) { | |
if (err) { | |
err.status = response ? response.statusCode : 400; | |
return onError(err, callback); | |
} | |
var error; | |
var content_type = response.headers['content-type']; | |
var data = (body || ''); | |
// CouchDB doesn't always return the right content-type for JSON data, so | |
// we check for ^{ and }$ (ignoring leading/trailing whitespace) | |
if (!options.binary && (options.json || !options.processData) && | |
typeof data !== 'object' && | |
(/json/.test(content_type) || | |
(/^[\s]*\{/.test(data) && /\}[\s]*$/.test(data)))) { | |
data = JSON.parse(data); | |
} | |
if (response.statusCode >= 200 && response.statusCode < 300) { | |
onSuccess(data, response, callback); | |
} | |
else { | |
if (options.binary) { | |
data = JSON.parse(data.toString()); | |
} | |
if (data.reason === 'missing') { | |
error = errors.MISSING_DOC; | |
} else if (data.reason === 'no_db_file') { | |
error = errors.error(errors.DB_MISSING, data.reason); | |
} else if (data.error === 'conflict') { | |
error = errors.REV_CONFLICT; | |
} else { | |
error = errors.error(errors.UNKNOWN_ERROR, data.reason, data.error); | |
} | |
error.status = response.statusCode; | |
callback(error); | |
} | |
}); | |
} | |
} | |
module.exports = ajax; | |
}).call(this,_dereq_("/Users/tom/Projects/src/node/pouchdb/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js")) | |
},{"../deps/uuid":12,"../utils":18,"./blob.js":7,"./errors":8,"./extend.js":10,"/Users/tom/Projects/src/node/pouchdb/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js":23,"request":21}],7:[function(_dereq_,module,exports){ | |
(function (global){ | |
"use strict"; | |
//Abstracts constructing a Blob object, so it also works in older | |
//browsers that don't support the native Blob constructor. (i.e. | |
//old QtWebKit versions, at least). | |
function createBlob(parts, properties) { | |
parts = parts || []; | |
properties = properties || {}; | |
try { | |
return new Blob(parts, properties); | |
} catch (e) { | |
if (e.name !== "TypeError") { | |
throw e; | |
} | |
var BlobBuilder = global.BlobBuilder || global.MSBlobBuilder || global.MozBlobBuilder || global.WebKitBlobBuilder; | |
var builder = new BlobBuilder(); | |
for (var i = 0; i < parts.length; i += 1) { | |
builder.append(parts[i]); | |
} | |
return builder.getBlob(properties.type); | |
} | |
} | |
module.exports = createBlob; | |
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{}],8:[function(_dereq_,module,exports){ | |
"use strict"; | |
function PouchError(opts) { | |
this.status = opts.status; | |
this.name = opts.error; | |
this.message = opts.reason; | |
this.error = true; | |
} | |
PouchError.prototype__proto__ = Error.prototype; | |
PouchError.prototype.toString = function () { | |
return JSON.stringify({ | |
status: this.status, | |
name: this.name, | |
message: this.message | |
}); | |
}; | |
exports.UNAUTHORIZED = new PouchError({ | |
status: 401, | |
error: 'unauthorized', | |
reason: "Name or password is incorrect." | |
}); | |
exports.MISSING_BULK_DOCS = new PouchError({ | |
status: 400, | |
error: 'bad_request', | |
reason: "Missing JSON list of 'docs'" | |
}); | |
exports.MISSING_DOC = new PouchError({ | |
status: 404, | |
error: 'not_found', | |
reason: 'missing' | |
}); | |
exports.REV_CONFLICT = new PouchError({ | |
status: 409, | |
error: 'conflict', | |
reason: 'Document update conflict' | |
}); | |
exports.INVALID_ID = new PouchError({ | |
status: 400, | |
error: 'invalid_id', | |
reason: '_id field must contain a string' | |
}); | |
exports.MISSING_ID = new PouchError({ | |
status: 412, | |
error: 'missing_id', | |
reason: '_id is required for puts' | |
}); | |
exports.RESERVED_ID = new PouchError({ | |
status: 400, | |
error: 'bad_request', | |
reason: 'Only reserved document ids may start with underscore.' | |
}); | |
exports.NOT_OPEN = new PouchError({ | |
status: 412, | |
error: 'precondition_failed', | |
reason: 'Database not open so cannot close' | |
}); | |
exports.UNKNOWN_ERROR = new PouchError({ | |
status: 500, | |
error: 'unknown_error', | |
reason: 'Database encountered an unknown error' | |
}); | |
exports.BAD_ARG = new PouchError({ | |
status: 500, | |
error: 'badarg', | |
reason: 'Some query argument is invalid' | |
}); | |
exports.INVALID_REQUEST = new PouchError({ | |
status: 400, | |
error: 'invalid_request', | |
reason: 'Request was invalid' | |
}); | |
exports.QUERY_PARSE_ERROR = new PouchError({ | |
status: 400, | |
error: 'query_parse_error', | |
reason: 'Some query parameter is invalid' | |
}); | |
exports.DOC_VALIDATION = new PouchError({ | |
status: 500, | |
error: 'doc_validation', | |
reason: 'Bad special document member' | |
}); | |
exports.BAD_REQUEST = new PouchError({ | |
status: 400, | |
error: 'bad_request', | |
reason: 'Something wrong with the request' | |
}); | |
exports.NOT_AN_OBJECT = new PouchError({ | |
status: 400, | |
error: 'bad_request', | |
reason: 'Document must be a JSON object' | |
}); | |
exports.DB_MISSING = new PouchError({ | |
status: 404, | |
error: 'not_found', | |
reason: 'Database not found' | |
}); | |
exports.IDB_ERROR = new PouchError({ | |
status: 500, | |
error: 'indexed_db_went_bad', | |
reason: 'unknown' | |
}); | |
exports.WSQ_ERROR = new PouchError({ | |
status: 500, | |
error: 'web_sql_went_bad', | |
reason: 'unknown' | |
}); | |
exports.LDB_ERROR = new PouchError({ | |
status: 500, | |
error: 'levelDB_went_went_bad', | |
reason: 'unknown' | |
}); | |
exports.FORBIDDEN = new PouchError({ | |
status: 403, | |
error: 'forbidden', | |
reason: 'Forbidden by design doc validate_doc_update function' | |
}); | |
exports.error = function (error, reason, name) { | |
function CustomPouchError(msg) { | |
this.message = reason; | |
if (name) { | |
this.name = name; | |
} | |
} | |
CustomPouchError.prototype = error; | |
return new CustomPouchError(reason); | |
}; | |
},{}],9:[function(_dereq_,module,exports){ | |
// some small shims for es5 just for the features we commonly use | |
// some of this is copied from https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js | |
'use strict'; | |
if (!Object.keys) { | |
Object.keys = function keys(object) { | |
if ((typeof object !== 'object' && typeof object !== 'function') || object === null) { | |
throw new TypeError('Object.keys called on a non-object'); | |
} | |
var mykeys = []; | |
for (var name in object) { | |
if (Object.prototype.hasOwnProperty.call(object, name)) { | |
mykeys.push(name); | |
} | |
} | |
return mykeys; | |
}; | |
} | |
if (!Array.isArray) { | |
Array.isArray = function isArray(obj) { | |
return Object.prototype.toString.call(obj) === '[object Array]'; | |
}; | |
} | |
if (!('forEach' in Array.prototype)) { | |
Array.prototype.forEach = function (action, that /*opt*/) { | |
for (var i = 0, n = this.length; i < n; i++) { | |
if (i in this) { | |
action.call(that, this[i], i, this); | |
} | |
} | |
}; | |
} | |
if (!('map' in Array.prototype)) { | |
Array.prototype.map = function (mapper, that /*opt*/) { | |
var other = new Array(this.length); | |
for (var i = 0, n = this.length; i < n; i++) { | |
if (i in this) { | |
other[i] = mapper.call(that, this[i], i, this); | |
} | |
} | |
return other; | |
}; | |
} | |
},{}],10:[function(_dereq_,module,exports){ | |
"use strict"; | |
// Extends method | |
// (taken from http://code.jquery.com/jquery-1.9.0.js) | |
// Populate the class2type map | |
var class2type = {}; | |
var types = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error"]; | |
for (var i = 0; i < types.length; i++) { | |
var typename = types[i]; | |
class2type["[object " + typename + "]"] = typename.toLowerCase(); | |
} | |
var core_toString = class2type.toString; | |
var core_hasOwn = class2type.hasOwnProperty; | |
function type(obj) { | |
if (obj === null) { | |
return String(obj); | |
} | |
return typeof obj === "object" || typeof obj === "function" ? | |
class2type[core_toString.call(obj)] || "object" : | |
typeof obj; | |
} | |
function isWindow(obj) { | |
return obj !== null && obj === obj.window; | |
} | |
function isPlainObject(obj) { | |
// Must be an Object. | |
// Because of IE, we also have to check the presence of the constructor property. | |
// Make sure that DOM nodes and window objects don't pass through, as well | |
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) { | |
return false; | |
} | |
try { | |
// Not own constructor property must be Object | |
if (obj.constructor && | |
!core_hasOwn.call(obj, "constructor") && | |
!core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { | |
return false; | |
} | |
} catch ( e ) { | |
// IE8,9 Will throw exceptions on certain host objects #9897 | |
return false; | |
} | |
// Own properties are enumerated firstly, so to speed up, | |
// if last one is own, then all properties are own. | |
var key; | |
for (key in obj) {} | |
return key === undefined || core_hasOwn.call(obj, key); | |
} | |
function isFunction(obj) { | |
return type(obj) === "function"; | |
} | |
var isArray = Array.isArray || function (obj) { | |
return type(obj) === "array"; | |
}; | |
function extend() { | |
var options, name, src, copy, copyIsArray, clone, | |
target = arguments[0] || {}, | |
i = 1, | |
length = arguments.length, | |
deep = false; | |
// Handle a deep copy situation | |
if (typeof target === "boolean") { | |
deep = target; | |
target = arguments[1] || {}; | |
// skip the boolean and the target | |
i = 2; | |
} | |
// Handle case when target is a string or something (possible in deep copy) | |
if (typeof target !== "object" && !isFunction(target)) { | |
target = {}; | |
} | |
// extend jQuery itself if only one argument is passed | |
if (length === i) { | |
/* jshint validthis: true */ | |
target = this; | |
--i; | |
} | |
for (; i < length; i++) { | |
// Only deal with non-null/undefined values | |
if ((options = arguments[i]) != null) { | |
// Extend the base object | |
for (name in options) { | |
//if (options.hasOwnProperty(name)) { | |
if (!(name in Object.prototype)) { | |
src = target[name]; | |
copy = options[name]; | |
// Prevent never-ending loop | |
if (target === copy) { | |
continue; | |
} | |
// Recurse if we're merging plain objects or arrays | |
if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) { | |
if (copyIsArray) { | |
copyIsArray = false; | |
clone = src && isArray(src) ? src : []; | |
} else { | |
clone = src && isPlainObject(src) ? src : {}; | |
} | |
// Never move original objects, clone them | |
target[name] = extend(deep, clone, copy); | |
// Don't bring in undefined values | |
} else if (copy !== undefined) { | |
if (!(isArray(options) && isFunction(copy))) { | |
target[name] = copy; | |
} | |
} | |
} | |
} | |
} | |
} | |
// Return the modified object | |
return target; | |
} | |
module.exports = extend; | |
},{}],11:[function(_dereq_,module,exports){ | |
(function (process){ | |
"use strict"; | |
/** | |
* | |
* MD5 (Message-Digest Algorithm) | |
* | |
* For original source see http://www.webtoolkit.info/ | |
* Download: 15.02.2009 from http://www.webtoolkit.info/javascript-md5.html | |
* | |
* Licensed under CC-BY 2.0 License | |
* (http://creativecommons.org/licenses/by/2.0/uk/) | |
* | |
**/ | |
var crypto = _dereq_('crypto'); | |
exports.MD5 = function (string) { | |
if (!process.browser) { | |
return crypto.createHash('md5').update(string).digest('hex'); | |
} | |
function rotateLeft(lValue, iShiftBits) { | |
return (lValue<<iShiftBits) | (lValue>>>(32 - iShiftBits)); | |
} | |
function addUnsigned(lX, lY) { | |
var lX4, lY4, lX8, lY8, lResult; | |
lX8 = (lX & 0x80000000); | |
lY8 = (lY & 0x80000000); | |
lX4 = (lX & 0x40000000); | |
lY4 = (lY & 0x40000000); | |
lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF); | |
if (lX4 & lY4) { | |
return (lResult ^ 0x80000000 ^ lX8 ^ lY8); | |
} | |
if (lX4 | lY4) { | |
if (lResult & 0x40000000) { | |
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); | |
} else { | |
return (lResult ^ 0x40000000 ^ lX8 ^ lY8); | |
} | |
} else { | |
return (lResult ^ lX8 ^ lY8); | |
} | |
} | |
function f(x, y, z) { return (x & y) | ((~x) & z); } | |
function g(x, y, z) { return (x & z) | (y & (~z)); } | |
function h(x, y, z) { return (x ^ y ^ z); } | |
function i(x, y, z) { return (y ^ (x | (~z))); } | |
function ff(a, b, c, d, x, s, ac) { | |
a = addUnsigned(a, addUnsigned(addUnsigned(f(b, c, d), x), ac)); | |
return addUnsigned(rotateLeft(a, s), b); | |
} | |
function gg(a, b, c, d, x, s, ac) { | |
a = addUnsigned(a, addUnsigned(addUnsigned(g(b, c, d), x), ac)); | |
return addUnsigned(rotateLeft(a, s), b); | |
} | |
function hh(a, b, c, d, x, s, ac) { | |
a = addUnsigned(a, addUnsigned(addUnsigned(h(b, c, d), x), ac)); | |
return addUnsigned(rotateLeft(a, s), b); | |
} | |
function ii(a, b, c, d, x, s, ac) { | |
a = addUnsigned(a, addUnsigned(addUnsigned(i(b, c, d), x), ac)); | |
return addUnsigned(rotateLeft(a, s), b); | |
} | |
function convertToWordArray(string) { | |
var lWordCount; | |
var lMessageLength = string.length; | |
var lNumberOfWords_temp1 = lMessageLength + 8; | |
var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64; | |
var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16; | |
var lWordArray = new Array(lNumberOfWords - 1); | |
var lBytePosition = 0; | |
var lByteCount = 0; | |
while (lByteCount < lMessageLength) { | |
lWordCount = (lByteCount - (lByteCount % 4)) / 4; | |
lBytePosition = (lByteCount % 4) * 8; | |
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<<lBytePosition)); | |
lByteCount++; | |
} | |
lWordCount = (lByteCount - (lByteCount % 4)) / 4; | |
lBytePosition = (lByteCount % 4) * 8; | |
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition); | |
lWordArray[lNumberOfWords - 2] = lMessageLength<<3; | |
lWordArray[lNumberOfWords - 1] = lMessageLength>>>29; | |
return lWordArray; | |
} | |
function wordToHex(lValue) { | |
var wordToHexValue = "", wordToHexValue_temp = "", lByte, lCount; | |
for (lCount = 0;lCount <= 3;lCount++) { | |
lByte = (lValue>>>(lCount * 8)) & 255; | |
wordToHexValue_temp = "0" + lByte.toString(16); | |
wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length - 2, 2); | |
} | |
return wordToHexValue; | |
} | |
//** function Utf8Encode(string) removed. Aready defined in pidcrypt_utils.js | |
var x = []; | |
var k, AA, BB, CC, DD, a, b, c, d; | |
var S11 = 7, S12 = 12, S13 = 17, S14 = 22; | |
var S21 = 5, S22 = 9, S23 = 14, S24 = 20; | |
var S31 = 4, S32 = 11, S33 = 16, S34 = 23; | |
var S41 = 6, S42 = 10, S43 = 15, S44 = 21; | |
// string = Utf8Encode(string); #function call removed | |
x = convertToWordArray(string); | |
a = 0x67452301; | |
b = 0xEFCDAB89; | |
c = 0x98BADCFE; | |
d = 0x10325476; | |
for (k = 0;k < x.length;k += 16) { | |
AA = a; | |
BB = b; | |
CC = c; | |
DD = d; | |
a = ff(a, b, c, d, x[k + 0], S11, 0xD76AA478); | |
d = ff(d, a, b, c, x[k + 1], S12, 0xE8C7B756); | |
c = ff(c, d, a, b, x[k + 2], S13, 0x242070DB); | |
b = ff(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE); | |
a = ff(a, b, c, d, x[k + 4], S11, 0xF57C0FAF); | |
d = ff(d, a, b, c, x[k + 5], S12, 0x4787C62A); | |
c = ff(c, d, a, b, x[k + 6], S13, 0xA8304613); | |
b = ff(b, c, d, a, x[k + 7], S14, 0xFD469501); | |
a = ff(a, b, c, d, x[k + 8], S11, 0x698098D8); | |
d = ff(d, a, b, c, x[k + 9], S12, 0x8B44F7AF); | |
c = ff(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1); | |
b = ff(b, c, d, a, x[k + 11], S14, 0x895CD7BE); | |
a = ff(a, b, c, d, x[k + 12], S11, 0x6B901122); | |
d = ff(d, a, b, c, x[k + 13], S12, 0xFD987193); | |
c = ff(c, d, a, b, x[k + 14], S13, 0xA679438E); | |
b = ff(b, c, d, a, x[k + 15], S14, 0x49B40821); | |
a = gg(a, b, c, d, x[k + 1], S21, 0xF61E2562); | |
d = gg(d, a, b, c, x[k + 6], S22, 0xC040B340); | |
c = gg(c, d, a, b, x[k + 11], S23, 0x265E5A51); | |
b = gg(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA); | |
a = gg(a, b, c, d, x[k + 5], S21, 0xD62F105D); | |
d = gg(d, a, b, c, x[k + 10], S22, 0x2441453); | |
c = gg(c, d, a, b, x[k + 15], S23, 0xD8A1E681); | |
b = gg(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8); | |
a = gg(a, b, c, d, x[k + 9], S21, 0x21E1CDE6); | |
d = gg(d, a, b, c, x[k + 14], S22, 0xC33707D6); | |
c = gg(c, d, a, b, x[k + 3], S23, 0xF4D50D87); | |
b = gg(b, c, d, a, x[k + 8], S24, 0x455A14ED); | |
a = gg(a, b, c, d, x[k + 13], S21, 0xA9E3E905); | |
d = gg(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8); | |
c = gg(c, d, a, b, x[k + 7], S23, 0x676F02D9); | |
b = gg(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A); | |
a = hh(a, b, c, d, x[k + 5], S31, 0xFFFA3942); | |
d = hh(d, a, b, c, x[k + 8], S32, 0x8771F681); | |
c = hh(c, d, a, b, x[k + 11], S33, 0x6D9D6122); | |
b = hh(b, c, d, a, x[k + 14], S34, 0xFDE5380C); | |
a = hh(a, b, c, d, x[k + 1], S31, 0xA4BEEA44); | |
d = hh(d, a, b, c, x[k + 4], S32, 0x4BDECFA9); | |
c = hh(c, d, a, b, x[k + 7], S33, 0xF6BB4B60); | |
b = hh(b, c, d, a, x[k + 10], S34, 0xBEBFBC70); | |
a = hh(a, b, c, d, x[k + 13], S31, 0x289B7EC6); | |
d = hh(d, a, b, c, x[k + 0], S32, 0xEAA127FA); | |
c = hh(c, d, a, b, x[k + 3], S33, 0xD4EF3085); | |
b = hh(b, c, d, a, x[k + 6], S34, 0x4881D05); | |
a = hh(a, b, c, d, x[k + 9], S31, 0xD9D4D039); | |
d = hh(d, a, b, c, x[k + 12], S32, 0xE6DB99E5); | |
c = hh(c, d, a, b, x[k + 15], S33, 0x1FA27CF8); | |
b = hh(b, c, d, a, x[k + 2], S34, 0xC4AC5665); | |
a = ii(a, b, c, d, x[k + 0], S41, 0xF4292244); | |
d = ii(d, a, b, c, x[k + 7], S42, 0x432AFF97); | |
c = ii(c, d, a, b, x[k + 14], S43, 0xAB9423A7); | |
b = ii(b, c, d, a, x[k + 5], S44, 0xFC93A039); | |
a = ii(a, b, c, d, x[k + 12], S41, 0x655B59C3); | |
d = ii(d, a, b, c, x[k + 3], S42, 0x8F0CCC92); | |
c = ii(c, d, a, b, x[k + 10], S43, 0xFFEFF47D); | |
b = ii(b, c, d, a, x[k + 1], S44, 0x85845DD1); | |
a = ii(a, b, c, d, x[k + 8], S41, 0x6FA87E4F); | |
d = ii(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0); | |
c = ii(c, d, a, b, x[k + 6], S43, 0xA3014314); | |
b = ii(b, c, d, a, x[k + 13], S44, 0x4E0811A1); | |
a = ii(a, b, c, d, x[k + 4], S41, 0xF7537E82); | |
d = ii(d, a, b, c, x[k + 11], S42, 0xBD3AF235); | |
c = ii(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB); | |
b = ii(b, c, d, a, x[k + 9], S44, 0xEB86D391); | |
a = addUnsigned(a, AA); | |
b = addUnsigned(b, BB); | |
c = addUnsigned(c, CC); | |
d = addUnsigned(d, DD); | |
} | |
var temp = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d); | |
return temp.toLowerCase(); | |
}; | |
}).call(this,_dereq_("/Users/tom/Projects/src/node/pouchdb/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js")) | |
},{"/Users/tom/Projects/src/node/pouchdb/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js":23,"crypto":20}],12:[function(_dereq_,module,exports){ | |
"use strict"; | |
// 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" | |
*/ | |
function uuid(len, radix) { | |
var chars = uuid.CHARS; | |
var uuidInner = []; | |
var i; | |
radix = radix || chars.length; | |
if (len) { | |
// Compact form | |
for (i = 0; i < len; i++) { | |
uuidInner[i] = chars[0 | Math.random() * radix]; | |
} | |
} else { | |
// rfc4122, version 4 form | |
var r; | |
// rfc4122 requires these characters | |
uuidInner[8] = uuidInner[13] = uuidInner[18] = uuidInner[23] = '-'; | |
uuidInner[14] = '4'; | |
// Fill in random data. At i==19 set the high bits of clock sequence as | |
// per rfc4122, sec. 4.1.5 | |
for (i = 0; i < 36; i++) { | |
if (!uuidInner[i]) { | |
r = 0 | Math.random() * 16; | |
uuidInner[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r]; | |
} | |
} | |
} | |
return uuidInner.join(''); | |
} | |
uuid.CHARS = ( | |
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + | |
'abcdefghijklmnopqrstuvwxyz' | |
).split(''); | |
module.exports = uuid; | |
},{}],13:[function(_dereq_,module,exports){ | |
(function (process){ | |
"use strict"; | |
_dereq_('./deps/es5_shims'); | |
var PouchDB = _dereq_('./setup'); | |
module.exports = PouchDB; | |
PouchDB.ajax = _dereq_('./deps/ajax'); | |
PouchDB.extend = _dereq_('./deps/extend'); | |
PouchDB.utils = _dereq_('./utils'); | |
PouchDB.Errors = _dereq_('./deps/errors'); | |
var replicate = _dereq_('./replicate'); | |
PouchDB.replicate = replicate.replicate; | |
PouchDB.sync = replicate.sync; | |
PouchDB.version = _dereq_('./version'); | |
var httpAdapter = _dereq_('./adapters/http'); | |
PouchDB.adapter('http', httpAdapter); | |
PouchDB.adapter('https', httpAdapter); | |
PouchDB.adapter('idb', _dereq_('./adapters/idb')); | |
PouchDB.adapter('websql', _dereq_('./adapters/websql')); | |
PouchDB.plugin(_dereq_('pouchdb-mapreduce')); | |
if (!process.browser) { | |
var ldbAdapter = _dereq_('./adapters/leveldb'); | |
PouchDB.adapter('ldb', ldbAdapter); | |
PouchDB.adapter('leveldb', ldbAdapter); | |
} | |
}).call(this,_dereq_("/Users/tom/Projects/src/node/pouchdb/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js")) | |
},{"./adapters/http":2,"./adapters/idb":3,"./adapters/websql":4,"./deps/ajax":6,"./deps/errors":8,"./deps/es5_shims":9,"./deps/extend":10,"./replicate":15,"./setup":16,"./utils":18,"./version":19,"/Users/tom/Projects/src/node/pouchdb/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js":23,"pouchdb-mapreduce":43}],14:[function(_dereq_,module,exports){ | |
'use strict'; | |
var extend = _dereq_('./deps/extend'); | |
// for a better overview of what this is doing, read: | |
// https://github.com/apache/couchdb/blob/master/src/couchdb/couch_key_tree.erl | |
// | |
// But for a quick intro, CouchDB uses a revision tree to store a documents | |
// history, A -> B -> C, when a document has conflicts, that is a branch in the | |
// tree, A -> (B1 | B2 -> C), We store these as a nested array in the format | |
// | |
// KeyTree = [Path ... ] | |
// Path = {pos: position_from_root, ids: Tree} | |
// Tree = [Key, Opts, [Tree, ...]], in particular single node: [Key, []] | |
// Turn a path as a flat array into a tree with a single branch | |
function pathToTree(path) { | |
var doc = path.shift(); | |
var root = [doc.id, doc.opts, []]; | |
var leaf = root; | |
var nleaf; | |
while (path.length) { | |
doc = path.shift(); | |
nleaf = [doc.id, doc.opts, []]; | |
leaf[2].push(nleaf); | |
leaf = nleaf; | |
} | |
return root; | |
} | |
// Merge two trees together | |
// The roots of tree1 and tree2 must be the same revision | |
function mergeTree(in_tree1, in_tree2) { | |
var queue = [{tree1: in_tree1, tree2: in_tree2}]; | |
var conflicts = false; | |
while (queue.length > 0) { | |
var item = queue.pop(); | |
var tree1 = item.tree1; | |
var tree2 = item.tree2; | |
if (tree1[1].status || tree2[1].status) { | |
tree1[1].status = (tree1[1].status === 'available' || | |
tree2[1].status === 'available') ? 'available' : 'missing'; | |
} | |
for (var i = 0; i < tree2[2].length; i++) { | |
if (!tree1[2][0]) { | |
conflicts = 'new_leaf'; | |
tree1[2][0] = tree2[2][i]; | |
continue; | |
} | |
var merged = false; | |
for (var j = 0; j < tree1[2].length; j++) { | |
if (tree1[2][j][0] === tree2[2][i][0]) { | |
queue.push({tree1: tree1[2][j], tree2: tree2[2][i]}); | |
merged = true; | |
} | |
} | |
if (!merged) { | |
conflicts = 'new_branch'; | |
tree1[2].push(tree2[2][i]); | |
tree1[2].sort(); | |
} | |
} | |
} | |
return {conflicts: conflicts, tree: in_tree1}; | |
} | |
function doMerge(tree, path, dontExpand) { | |
var restree = []; | |
var conflicts = false; | |
var merged = false; | |
var res; | |
if (!tree.length) { | |
return {tree: [path], conflicts: 'new_leaf'}; | |
} | |
tree.forEach(function (branch) { | |
if (branch.pos === path.pos && branch.ids[0] === path.ids[0]) { | |
// Paths start at the same position and have the same root, so they need | |
// merged | |
res = mergeTree(branch.ids, path.ids); | |
restree.push({pos: branch.pos, ids: res.tree}); | |
conflicts = conflicts || res.conflicts; | |
merged = true; | |
} else if (dontExpand !== true) { | |
// The paths start at a different position, take the earliest path and | |
// traverse up until it as at the same point from root as the path we want to | |
// merge. If the keys match we return the longer path with the other merged | |
// After stemming we dont want to expand the trees | |
var t1 = branch.pos < path.pos ? branch : path; | |
var t2 = branch.pos < path.pos ? path : branch; | |
var diff = t2.pos - t1.pos; | |
var candidateParents = []; | |
var trees = []; | |
trees.push({ids: t1.ids, diff: diff, parent: null, parentIdx: null}); | |
while (trees.length > 0) { | |
var item = trees.pop(); | |
if (item.diff === 0) { | |
if (item.ids[0] === t2.ids[0]) { | |
candidateParents.push(item); | |
} | |
continue; | |
} | |
if (!item.ids) { | |
continue; | |
} | |
/*jshint loopfunc:true */ | |
item.ids[2].forEach(function (el, idx) { | |
trees.push({ids: el, diff: item.diff - 1, parent: item.ids, parentIdx: idx}); | |
}); | |
} | |
var el = candidateParents[0]; | |
if (!el) { | |
restree.push(branch); | |
} else { | |
res = mergeTree(el.ids, t2.ids); | |
el.parent[2][el.parentIdx] = res.tree; | |
restree.push({pos: t1.pos, ids: t1.ids}); | |
conflicts = conflicts || res.conflicts; | |
merged = true; | |
} | |
} else { | |
restree.push(branch); | |
} | |
}); | |
// We didnt find | |
if (!merged) { | |
restree.push(path); | |
} | |
restree.sort(function (a, b) { | |
return a.pos - b.pos; | |
}); | |
return { | |
tree: restree, | |
conflicts: conflicts || 'internal_node' | |
}; | |
} | |
// To ensure we dont grow the revision tree infinitely, we stem old revisions | |
function stem(tree, depth) { | |
// First we break out the tree into a complete list of root to leaf paths, | |
// we cut off the start of the path and generate a new set of flat trees | |
var stemmedPaths = PouchMerge.rootToLeaf(tree).map(function (path) { | |
var stemmed = path.ids.slice(-depth); | |
return { | |
pos: path.pos + (path.ids.length - stemmed.length), | |
ids: pathToTree(stemmed) | |
}; | |
}); | |
// Then we remerge all those flat trees together, ensuring that we dont | |
// connect trees that would go beyond the depth limit | |
return stemmedPaths.reduce(function (prev, current, i, arr) { | |
return doMerge(prev, current, true).tree; | |
}, [stemmedPaths.shift()]); | |
} | |
var PouchMerge = {}; | |
PouchMerge.merge = function (tree, path, depth) { | |
// Ugh, nicer way to not modify arguments in place? | |
tree = extend(true, [], tree); | |
path = extend(true, {}, path); | |
var newTree = doMerge(tree, path); | |
return { | |
tree: stem(newTree.tree, depth), | |
conflicts: newTree.conflicts | |
}; | |
}; | |
// We fetch all leafs of the revision tree, and sort them based on tree length | |
// and whether they were deleted, undeleted documents with the longest revision | |
// tree (most edits) win | |
// The final sort algorithm is slightly documented in a sidebar here: | |
// http://guide.couchdb.org/draft/conflicts.html | |
PouchMerge.winningRev = function (metadata) { | |
var leafs = []; | |
PouchMerge.traverseRevTree(metadata.rev_tree, | |
function (isLeaf, pos, id, something, opts) { | |
if (isLeaf) { | |
leafs.push({pos: pos, id: id, deleted: !!opts.deleted}); | |
} | |
}); | |
leafs.sort(function (a, b) { | |
if (a.deleted !== b.deleted) { | |
return a.deleted > b.deleted ? 1 : -1; | |
} | |
if (a.pos !== b.pos) { | |
return b.pos - a.pos; | |
} | |
return a.id < b.id ? 1 : -1; | |
}); | |
return leafs[0].pos + '-' + leafs[0].id; | |
}; | |
// Pretty much all below can be combined into a higher order function to | |
// traverse revisions | |
// The return value from the callback will be passed as context to all | |
// children of that node | |
PouchMerge.traverseRevTree = function (revs, callback) { | |
var toVisit = []; | |
revs.forEach(function (tree) { | |
toVisit.push({pos: tree.pos, ids: tree.ids}); | |
}); | |
while (toVisit.length > 0) { | |
var node = toVisit.pop(); | |
var pos = node.pos; | |
var tree = node.ids; | |
var newCtx = callback(tree[2].length === 0, pos, tree[0], node.ctx, tree[1]); | |
/*jshint loopfunc: true */ | |
tree[2].forEach(function (branch) { | |
toVisit.push({pos: pos + 1, ids: branch, ctx: newCtx}); | |
}); | |
} | |
}; | |
PouchMerge.collectLeaves = function (revs) { | |
var leaves = []; | |
PouchMerge.traverseRevTree(revs, function (isLeaf, pos, id, acc, opts) { | |
if (isLeaf) { | |
leaves.unshift({rev: pos + "-" + id, pos: pos, opts: opts}); | |
} | |
}); | |
leaves.sort(function (a, b) { | |
return b.pos - a.pos; | |
}); | |
leaves.map(function (leaf) { delete leaf.pos; }); | |
return leaves; | |
}; | |
// returns revs of all conflicts that is leaves such that | |
// 1. are not deleted and | |
// 2. are different than winning revision | |
PouchMerge.collectConflicts = function (metadata) { | |
var win = PouchMerge.winningRev(metadata); | |
var leaves = PouchMerge.collectLeaves(metadata.rev_tree); | |
var conflicts = []; | |
leaves.forEach(function (leaf) { | |
if (leaf.rev !== win && !leaf.opts.deleted) { | |
conflicts.push(leaf.rev); | |
} | |
}); | |
return conflicts; | |
}; | |
PouchMerge.rootToLeaf = function (tree) { | |
var paths = []; | |
PouchMerge.traverseRevTree(tree, function (isLeaf, pos, id, history, opts) { | |
history = history ? history.slice(0) : []; | |
history.push({id: id, opts: opts}); | |
if (isLeaf) { | |
var rootPos = pos + 1 - history.length; | |
paths.unshift({pos: rootPos, ids: history}); | |
} | |
return history; | |
}); | |
return paths; | |
}; | |
module.exports = PouchMerge; | |
},{"./deps/extend":10}],15:[function(_dereq_,module,exports){ | |
'use strict'; | |
var PouchUtils = _dereq_('./utils'); | |
var Pouch = _dereq_('./index'); | |
// We create a basic promise so the caller can cancel the replication possibly | |
// before we have actually started listening to changes etc | |
function Promise() { | |
var that = this; | |
this.cancelled = false; | |
this.cancel = function () { | |
that.cancelled = true; | |
}; | |
} | |
// A batch of changes to be processed as a unit | |
function Batch() { | |
this.seq = 0; | |
this.state = 'start'; | |
this.changes = []; | |
this.docs = []; | |
} | |
// TODO: check CouchDB's replication id generation | |
// Generate a unique id particular to this replication | |
function genReplicationId(src, target, opts, callback) { | |
var filterFun = opts.filter ? opts.filter.toString() : ''; | |
src.id(function (err, src_id) { | |
target.id(function (err, target_id) { | |
var queryData = src_id + target_id + filterFun + | |
JSON.stringify(opts.query_params) + opts.doc_ids; | |
callback('_local/' + PouchUtils.Crypto.MD5(queryData)); | |
}); | |
}); | |
} | |
// A checkpoint lets us restart replications from when they were last cancelled | |
function fetchCheckpoint(src, target, id, callback) { | |
target.get(id, function (err, targetDoc) { | |
if (err && err.status === 404) { | |
callback(null, 0); | |
} else if (err) { | |
callback(err); | |
} else { | |
src.get(id, function (err, sourceDoc) { | |
if (err && err.status === 404 || | |
(!err && (targetDoc.last_seq !== sourceDoc.last_seq))) { | |
callback(null, 0); | |
} else if (err) { | |
callback(err); | |
} else { | |
callback(null, sourceDoc.last_seq); | |
} | |
}); | |
} | |
}); | |
} | |
function writeCheckpoint(src, target, id, checkpoint, callback) { | |
function updateCheckpoint(db, callback) { | |
db.get(id, function (err, doc) { | |
if (err && err.status === 404) { | |
doc = {_id: id}; | |
} else if (err) { | |
return callback(err); | |
} | |
doc.last_seq = checkpoint; | |
db.put(doc, callback); | |
}); | |
} | |
updateCheckpoint(target, function (err, doc) { | |
if (err) { return callback(err); } | |
updateCheckpoint(src, function (err, doc) { | |
if (err) { return callback(err); } | |
callback(); | |
}); | |
}); | |
} | |
function replicate(repId, src, target, opts, promise) { | |
var batches = []; // queue of batches of changes to be processed | |
var pendingBatch = new Batch(); | |
var writingCheckpoint = false; | |
var changesCompleted = false; | |
var completeCalled = false; | |
var last_seq = 0; | |
var continuous = opts.continuous || opts.live || false; | |
var batch_size = opts.batch_size || 1; | |
var doc_ids = opts.doc_ids; | |
var result = { | |
ok: true, | |
start_time: new Date(), | |
docs_read: 0, | |
docs_written: 0, | |
doc_write_failures: 0, | |
errors: [] | |
}; | |
function writeDocs() { | |
if (batches[0].docs.length === 0) { | |
// This should never happen: | |
// batch processing continues past onRevsDiff only if there are diffs | |
// and replication is aborted if a get fails. | |
// TODO: throw or log the error | |
return finishBatch(); | |
} | |
var docs = batches[0].docs; | |
target.bulkDocs({docs: docs}, {new_edits: false}, function (err, res) { | |
if (err) { | |
result.doc_write_failures += docs.length; | |
return abortReplication('target.bulkDocs completed with error', err); | |
} | |
var errors = []; | |
res.forEach(function (res) { | |
if (!res.ok) { | |
result.doc_write_failures++; | |
errors.push({ | |
status: 500, | |
error: res.error || 'Unknown document write error', | |
reason: res.reason || 'Unknown reason', | |
}); | |
} | |
}); | |
if (errors.length > 0) { | |
return abortReplication('target.bulkDocs failed to write docs', errors); | |
} | |
result.docs_written += docs.length; | |
finishBatch(); | |
}); | |
} | |
function onGet(err, docs) { | |
if (promise.cancelled) { | |
return cancelReplication(); | |
} | |
if (err) { | |
return abortReplication('src.get completed with error', err); | |
} | |
Object.keys(docs).forEach(function (revpos) { | |
var doc = docs[revpos].ok; | |
if (doc) { | |
result.docs_read++; | |
batches[0].pendingRevs++; | |
batches[0].docs.push(doc); | |
} | |
}); | |
fetchRev(); | |
} | |
function fetchRev() { | |
var diffs = batches[0].diffs; | |
if (Object.keys(diffs).length === 0) { | |
writeDocs(); | |
return; | |
} | |
var id = Object.keys(diffs)[0]; | |
var revs = diffs[id].missing; | |
delete diffs[id]; | |
src.get(id, {revs: true, open_revs: revs, attachments: true}, onGet); | |
} | |
function abortReplication(reason, err) { | |
if (completeCalled) { | |
return; | |
} | |
result.ok = false; | |
result.status = 'aborted'; | |
result.errors.push(err); | |
result.end_time = new Date(); | |
result.last_seq = last_seq; | |
batches = []; | |
pendingBatch = new Batch(); | |
var error = { | |
status: 500, | |
error: 'Replication aborted', | |
reason: reason, | |
details: err | |
}; | |
completeCalled = true; | |
PouchUtils.call(opts.complete, error, result); | |
promise.cancel(); | |
} | |
function finishBatch() { | |
writingCheckpoint = true; | |
writeCheckpoint(src, target, repId, batches[0].seq, function (err, res) { | |
writingCheckpoint = false; | |
if (promise.cancelled) { | |
return cancelReplication(); | |
} | |
if (err) { | |
return abortReplication('writeCheckpoint completed with error', err); | |
} | |
last_seq = batches[0].seq; | |
result.last_seq = last_seq; | |
PouchUtils.call(opts.onChange, null, result); | |
batches.shift(); | |
startNextBatch(); | |
}); | |
} | |
function onRevsDiff(err, diffs) { | |
if (promise.cancelled) { | |
return cancelReplication(); | |
} | |
if (err) { | |
return abortReplication('target.revsDiff completed with error', err); | |
} | |
if (Object.keys(diffs).length === 0) { | |
finishBatch(); | |
return; | |
} | |
batches[0].diffs = diffs; | |
batches[0].pendingRevs = 0; | |
fetchRev(); | |
} | |
function fetchRevsDiff() { | |
var diff = {}; | |
batches[0].changes.forEach(function (change) { | |
diff[change.id] = change.changes.map(function (x) { return x.rev; }); | |
}); | |
target.revsDiff(diff, onRevsDiff); | |
} | |
function startNextBatch() { | |
if (promise.cancelled) { | |
return cancelReplication(); | |
} | |
if (batches.length === 0) { | |
processPendingBatch(); | |
return; | |
} | |
if (batches[0].state === 'start') { | |
batches[0].state = 'processing'; | |
fetchRevsDiff(); | |
} | |
} | |
function processPendingBatch() { | |
if (pendingBatch.changes.length === 0) { | |
if (changesCompleted && batches.length === 0) { | |
replicationComplete(); | |
} | |
return; | |
} | |
if (changesCompleted || pendingBatch.changes.length >= batch_size) { | |
batches.push(pendingBatch); | |
pendingBatch = new Batch(); | |
startNextBatch(); | |
} | |
} | |
function cancelReplication() { | |
result.status = 'cancelled'; | |
if (!writingCheckpoint) { | |
replicationComplete(); | |
} | |
} | |
function replicationComplete() { | |
if (completeCalled) { | |
return; | |
} | |
result.status = result.status || 'complete'; | |
result.end_time = new Date(); | |
result.last_seq = last_seq; | |
completeCalled = true; | |
return PouchUtils.call(opts.complete, null, result); | |
} | |
function onChange(change) { | |
if (promise.cancelled) { | |
return cancelReplication(); | |
} | |
if (completeCalled) { | |
// This should never happen | |
// The complete callback has already been called | |
// How to raise an exception in PouchDB? | |
return; | |
} | |
pendingBatch.seq = change.seq; | |
pendingBatch.changes.push(change); | |
processPendingBatch(); | |
} | |
function complete(err, result) { | |
changesCompleted = true; | |
if (promise.cancelled) { | |
return cancelReplication(); | |
} | |
if (err) { | |
return abortReplication('src.changes completed with error', err); | |
} | |
processPendingBatch(); | |
} | |
function getChanges() { | |
fetchCheckpoint(src, target, repId, function (err, checkpoint) { | |
if (err) { | |
return abortReplication('fetchCheckpoint completed with error', err); | |
} | |
last_seq = checkpoint; | |
// Was the replication cancelled by the caller before it had a chance | |
// to start. Shouldnt we be calling complete? | |
if (promise.cancelled) { | |
return cancelReplication(); | |
} | |
// Call changes on the source database, with callbacks to onChange for | |
// each change and complete when done. | |
var repOpts = { | |
continuous: continuous, | |
since: last_seq, | |
style: 'all_docs', | |
onChange: onChange, | |
complete: complete, | |
doc_ids: doc_ids | |
}; | |
if (opts.filter) { | |
repOpts.filter = opts.filter; | |
} | |
if (opts.query_params) { | |
repOpts.query_params = opts.query_params; | |
} | |
var changes = src.changes(repOpts); | |
var cancelPromise = promise.cancel; | |
promise.cancel = function () { | |
cancelPromise(); | |
cancelReplication(); | |
if (changes && changes.cancel instanceof Function) { | |
changes.cancel(); | |
} | |
}; | |
}); | |
} | |
// If opts.since is given, set the checkpoint to opts.since | |
if (typeof opts.since === 'undefined') { | |
getChanges(); | |
} else { | |
writeCheckpoint(src, target, repId, opts.since, function (err, res) { | |
if (err) { | |
return abortReplication('writeCheckpoint completed with error', err); | |
} | |
last_seq = opts.since; | |
getChanges(); | |
}); | |
} | |
} | |
function toPouch(db, callback) { | |
if (typeof db === 'string') { | |
return new Pouch(db, callback); | |
} | |
callback(null, db); | |
} | |
function replicateWrapper(src, target, opts, callback) { | |
if (opts instanceof Function) { | |
callback = opts; | |
opts = {}; | |
} | |
if (opts === undefined) { | |
opts = {}; | |
} | |
if (!opts.complete) { | |
opts.complete = callback; | |
} | |
opts = PouchUtils.extend(true, {}, opts); | |
opts.continuous = opts.continuous || opts.live; | |
var replicateRet = new Promise(); | |
toPouch(src, function (err, src) { | |
if (err) { | |
return callback(err); | |
} | |
toPouch(target, function (err, target) { | |
if (err) { | |
return callback(err); | |
} | |
if (opts.server) { | |
if (typeof src.replicateOnServer !== 'function') { | |
return callback({ | |
error: 'Server replication not supported for ' + src.type() + | |
'adapter' | |
}); | |
} | |
if (src.type() !== target.type()) { | |
return callback({ | |
error: 'Server replication for different adapter types (' + | |
src.type() + ' and ' + target.type() + ') is not supported' | |
}); | |
} | |
src.replicateOnServer(target, opts, replicateRet); | |
} else { | |
genReplicationId(src, target, opts, function (repId) { | |
replicate(repId, src, target, opts, replicateRet); | |
}); | |
} | |
}); | |
}); | |
return replicateRet; | |
} | |
function sync(db1, db2, opts, callback) { | |
var push_promise; | |
var pull_promise; | |
if (opts instanceof Function) { | |
callback = opts; | |
opts = {}; | |
} | |
if (opts === undefined) { | |
opts = {}; | |
} | |
if (callback instanceof Function && !opts.complete) { | |
opts.complete = callback; | |
} | |
function complete(callback, direction) { | |
return function (err, res) { | |
if (err) { | |
// cancel both replications if either experiences problems | |
cancel(); | |
} | |
res.direction = direction; | |
callback(err, res); | |
}; | |
} | |
function onChange(src, callback) { | |
callback = callback || function () {}; | |
return function (change) { | |
return { | |
source: src, | |
change: callback(change) | |
}; | |
}; | |
} | |
function makeOpts(src, opts, direction) { | |
opts = PouchUtils.extend(true, {}, opts); | |
opts.complete = complete(opts.complete, direction); | |
opts.onChange = onChange(src, opts.onChange); | |
opts.continuous = opts.continuous || opts.live; | |
return opts; | |
} | |
function push() { | |
push_promise = replicateWrapper(db1, db2, makeOpts(db1, opts, 'push'), callback); | |
return push_promise; | |
} | |
function pull() { | |
pull_promise = replicateWrapper(db2, db1, makeOpts(db2, opts, 'pull'), callback); | |
return pull_promise; | |
} | |
function cancel() { | |
if (push_promise) { | |
push_promise.cancel(); | |
} | |
if (pull_promise) { | |
pull_promise.cancel(); | |
} | |
} | |
return { | |
push: push(), | |
pull: pull(), | |
cancel: cancel | |
}; | |
} | |
exports.replicate = replicateWrapper; | |
exports.sync = sync; | |
},{"./index":13,"./utils":18}],16:[function(_dereq_,module,exports){ | |
(function (global){ | |
"use strict"; | |
var PouchDB = _dereq_("./constructor"); | |
var utils = _dereq_('./utils'); | |
var EventEmitter = _dereq_('events').EventEmitter; | |
PouchDB.adapters = {}; | |
PouchDB.prefix = '_pouch_'; | |
var eventEmitter = new EventEmitter(); | |
var eventEmitterMethods = [ | |
'on', | |
'addListener', | |
'emit', | |
'listeners', | |
'once', | |
'removeAllListeners', | |
'removeListener', | |
'setMaxListeners' | |
]; | |
var preferredAdapters = ['levelalt', 'idb', 'leveldb', 'websql']; | |
eventEmitterMethods.forEach(function (method) { | |
PouchDB[method] = eventEmitter[method].bind(eventEmitter); | |
}); | |
PouchDB.setMaxListeners(0); | |
PouchDB.parseAdapter = function (name, opts) { | |
var match = name.match(/([a-z\-]*):\/\/(.*)/); | |
var adapter, adapterName; | |
if (match) { | |
// the http adapter expects the fully qualified name | |
name = /http(s?)/.test(match[1]) ? match[1] + '://' + match[2] : match[2]; | |
adapter = match[1]; | |
if (!PouchDB.adapters[adapter].valid()) { | |
throw 'Invalid adapter'; | |
} | |
return {name: name, adapter: match[1]}; | |
} | |
// check for browsers that have been upgraded from websql-only to websql+idb | |
var skipIdb = 'idb' in PouchDB.adapters && 'websql' in PouchDB.adapters && | |
utils.hasLocalStorage() && | |
global.localStorage['_pouch__websqldb_' + PouchDB.prefix + name]; | |
if (typeof opts !== 'undefined' && opts.db) { | |
adapterName = 'leveldb'; | |
} else { | |
for (var i = 0; i < preferredAdapters.length; ++i) { | |
adapterName = preferredAdapters[i]; | |
if (adapterName in PouchDB.adapters) { | |
if (skipIdb && adapterName === 'idb') { | |
continue; // keep using websql to avoid user data loss | |
} | |
break; | |
} | |
} | |
} | |
if (adapterName) { | |
adapter = PouchDB.adapters[adapterName]; | |
var use_prefix = 'use_prefix' in adapter ? adapter.use_prefix : true; | |
return { | |
name: use_prefix ? PouchDB.prefix + name : name, | |
adapter: adapterName | |
}; | |
} | |
throw 'No valid adapter found'; | |
}; | |
PouchDB.destroy = utils.toPromise(function (name, opts, callback) { | |
if (typeof opts === 'function' || typeof opts === 'undefined') { | |
callback = opts; | |
opts = {}; | |
} | |
if (typeof name === 'object') { | |
opts = name; | |
name = undefined; | |
} | |
var backend = PouchDB.parseAdapter(opts.name || name, opts); | |
var dbName = backend.name; | |
// call destroy method of the particular adaptor | |
PouchDB.adapters[backend.adapter].destroy(dbName, opts, function (err, resp) { | |
if (err) { | |
callback(err); | |
} else { | |
PouchDB.emit('destroyed', dbName); | |
//so we don't have to sift through all dbnames | |
PouchDB.emit(dbName, 'destroyed'); | |
callback(null, resp); | |
} | |
}); | |
}); | |
PouchDB.allDbs = utils.toPromise(function (callback) { | |
var err = new Error('allDbs method removed'); | |
err.stats = '400'; | |
callback(err); | |
}); | |
PouchDB.adapter = function (id, obj) { | |
if (obj.valid()) { | |
PouchDB.adapters[id] = obj; | |
} | |
}; | |
PouchDB.plugin = function (obj) { | |
Object.keys(obj).forEach(function (id) { | |
PouchDB.prototype[id] = obj[id]; | |
}); | |
}; | |
module.exports = PouchDB; | |
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{"./constructor":5,"./utils":18,"events":22}],17:[function(_dereq_,module,exports){ | |
'use strict'; | |
module.exports = TaskQueue; | |
function TaskQueue() { | |
this.isReady = false; | |
this.failed = false; | |
this.queue = []; | |
} | |
TaskQueue.prototype.execute = function () { | |
var d, func; | |
if (this.failed) { | |
while ((d = this.queue.shift())) { | |
func = d.parameters[d.parameters.length - 1]; | |
if (typeof func === 'function') { | |
func(this.failed); | |
} else if (d.name === 'changes' && typeof func.complete === 'function') { | |
func.complete(this.failed); | |
} | |
} | |
} else if (this.isReady) { | |
while ((d = this.queue.shift())) { | |
if (typeof d === 'function') { | |
d(); | |
} else { | |
d.task = this.db[d.name].apply(this.db, d.parameters); | |
} | |
} | |
} | |
}; | |
TaskQueue.prototype.fail = function (err) { | |
this.failed = err; | |
this.execute(); | |
}; | |
TaskQueue.prototype.ready = function (db) { | |
if (this.failed) { | |
return false; | |
} else if (arguments.length === 0) { | |
return this.isReady; | |
} | |
this.isReady = db ? true: false; | |
this.db = db; | |
this.execute(); | |
}; | |
TaskQueue.prototype.addTask = function (name, parameters) { | |
if (typeof name === 'function') { | |
this.queue.push(name); | |
} else { | |
var task = { name: name, parameters: parameters }; | |
this.queue.push(task); | |
if (this.failed) { | |
this.execute(); | |
} | |
return task; | |
} | |
}; | |
},{}],18:[function(_dereq_,module,exports){ | |
(function (process,global){ | |
/*jshint strict: false */ | |
/*global chrome */ | |
var merge = _dereq_('./merge'); | |
exports.extend = _dereq_('./deps/extend'); | |
exports.ajax = _dereq_('./deps/ajax'); | |
exports.createBlob = _dereq_('./deps/blob'); | |
var uuid = _dereq_('./deps/uuid'); | |
exports.Crypto = _dereq_('./deps/md5.js'); | |
var buffer = _dereq_('./deps/buffer'); | |
var errors = _dereq_('./deps/errors'); | |
var EventEmitter = _dereq_('events').EventEmitter; | |
var Promise = typeof global.Promise === 'function' ? global.Promise : _dereq_('bluebird'); | |
// List of top level reserved words for doc | |
var reservedWords = [ | |
'_id', | |
'_rev', | |
'_attachments', | |
'_deleted', | |
'_revisions', | |
'_revs_info', | |
'_conflicts', | |
'_deleted_conflicts', | |
'_local_seq', | |
'_rev_tree' | |
]; | |
exports.inherits = _dereq_('inherits'); | |
exports.uuids = function (count, options) { | |
if (typeof(options) !== 'object') { | |
options = {}; | |
} | |
var length = options.length; | |
var radix = options.radix; | |
var uuids = []; | |
while (uuids.push(uuid(length, radix)) < count) { } | |
return uuids; | |
}; | |
// Give back one UUID | |
exports.uuid = function (options) { | |
return exports.uuids(1, options)[0]; | |
}; | |
// Determine id an ID is valid | |
// - invalid IDs begin with an underescore that does not begin '_design' or '_local' | |
// - any other string value is a valid id | |
// Returns the specific error object for each case | |
exports.invalidIdError = function (id) { | |
if (!id) { | |
return errors.MISSING_ID; | |
} else if (typeof id !== 'string') { | |
return errors.INVALID_ID; | |
} else if (/^_/.test(id) && !(/^_(design|local)/).test(id)) { | |
return errors.RESERVED_ID; | |
} | |
}; | |
function isChromeApp() { | |
return (typeof chrome !== "undefined" && | |
typeof chrome.storage !== "undefined" && | |
typeof chrome.storage.local !== "undefined"); | |
} | |
exports.getArguments = function (fun) { | |
return function () { | |
var len = arguments.length; | |
var args = new Array(len); | |
var i = -1; | |
while (++i < len) { | |
args[i] = arguments[i]; | |
} | |
return fun.call(this, args); | |
}; | |
}; | |
// Pretty dumb name for a function, just wraps callback calls so we dont | |
// to if (callback) callback() everywhere | |
exports.call = exports.getArguments(function (args) { | |
if (!args.length) { | |
return; | |
} | |
var fun = args.shift(); | |
if (typeof fun === 'function') { | |
fun.apply(this, args); | |
} | |
}); | |
exports.isLocalId = function (id) { | |
return (/^_local/).test(id); | |
}; | |
// check if a specific revision of a doc has been deleted | |
// - metadata: the metadata object from the doc store | |
// - rev: (optional) the revision to check. defaults to winning revision | |
exports.isDeleted = function (metadata, rev) { | |
if (!rev) { | |
rev = merge.winningRev(metadata); | |
} | |
if (rev.indexOf('-') >= 0) { | |
rev = rev.split('-')[1]; | |
} | |
var deleted = false; | |
merge.traverseRevTree(metadata.rev_tree, function (isLeaf, pos, id, acc, opts) { | |
if (id === rev) { | |
deleted = !!opts.deleted; | |
} | |
}); | |
return deleted; | |
}; | |
exports.filterChange = function (opts) { | |
return function (change) { | |
var req = {}; | |
var hasFilter = opts.filter && typeof opts.filter === 'function'; | |
req.query = opts.query_params; | |
if (opts.filter && hasFilter && !opts.filter.call(this, change.doc, req)) { | |
return false; | |
} | |
if (opts.doc_ids && opts.doc_ids.indexOf(change.id) === -1) { | |
return false; | |
} | |
if (!opts.include_docs) { | |
delete change.doc; | |
} else { | |
for (var att in change.doc._attachments) { | |
if (change.doc._attachments.hasOwnProperty(att)) { | |
change.doc._attachments[att].stub = true; | |
} | |
} | |
} | |
return true; | |
}; | |
}; | |
exports.processChanges = function (opts, changes, last_seq) { | |
// TODO: we should try to filter and limit as soon as possible | |
changes = changes.filter(exports.filterChange(opts)); | |
if (opts.limit) { | |
if (opts.limit < changes.length) { | |
changes.length = opts.limit; | |
} | |
} | |
changes.forEach(function (change) { | |
exports.call(opts.onChange, change); | |
}); | |
if (!opts.continuous) { | |
exports.call(opts.complete, null, {results: changes, last_seq: last_seq}); | |
} | |
}; | |
// Preprocess documents, parse their revisions, assign an id and a | |
// revision for new writes that are missing them, etc | |
exports.parseDoc = function (doc, newEdits) { | |
var error = null; | |
var nRevNum; | |
var newRevId; | |
var revInfo; | |
var opts = {status: 'available'}; | |
if (doc._deleted) { | |
opts.deleted = true; | |
} | |
if (newEdits) { | |
if (!doc._id) { | |
doc._id = exports.uuid(); | |
} | |
newRevId = exports.uuid({length: 32, radix: 16}).toLowerCase(); | |
if (doc._rev) { | |
revInfo = /^(\d+)-(.+)$/.exec(doc._rev); | |
if (!revInfo) { | |
throw "invalid value for property '_rev'"; | |
} | |
doc._rev_tree = [{ | |
pos: parseInt(revInfo[1], 10), | |
ids: [revInfo[2], {status: 'missing'}, [[newRevId, opts, []]]] | |
}]; | |
nRevNum = parseInt(revInfo[1], 10) + 1; | |
} else { | |
doc._rev_tree = [{ | |
pos: 1, | |
ids : [newRevId, opts, []] | |
}]; | |
nRevNum = 1; | |
} | |
} else { | |
if (doc._revisions) { | |
doc._rev_tree = [{ | |
pos: doc._revisions.start - doc._revisions.ids.length + 1, | |
ids: doc._revisions.ids.reduce(function (acc, x) { | |
if (acc === null) { | |
return [x, opts, []]; | |
} else { | |
return [x, {status: 'missing'}, [acc]]; | |
} | |
}, null) | |
}]; | |
nRevNum = doc._revisions.start; | |
newRevId = doc._revisions.ids[0]; | |
} | |
if (!doc._rev_tree) { | |
revInfo = /^(\d+)-(.+)$/.exec(doc._rev); | |
if (!revInfo) { | |
return errors.BAD_ARG; | |
} | |
nRevNum = parseInt(revInfo[1], 10); | |
newRevId = revInfo[2]; | |
doc._rev_tree = [{ | |
pos: parseInt(revInfo[1], 10), | |
ids: [revInfo[2], opts, []] | |
}]; | |
} | |
} | |
error = exports.invalidIdError(doc._id); | |
for (var key in doc) { | |
if (doc.hasOwnProperty(key) && key[0] === '_' && reservedWords.indexOf(key) === -1) { | |
error = exports.extend({}, errors.DOC_VALIDATION); | |
error.reason += ': ' + key; | |
} | |
} | |
doc._id = decodeURIComponent(doc._id); | |
doc._rev = [nRevNum, newRevId].join('-'); | |
if (error) { | |
return error; | |
} | |
return Object.keys(doc).reduce(function (acc, key) { | |
if (/^_/.test(key) && key !== '_attachments') { | |
acc.metadata[key.slice(1)] = doc[key]; | |
} else { | |
acc.data[key] = doc[key]; | |
} | |
return acc; | |
}, {metadata : {}, data : {}}); | |
}; | |
exports.isCordova = function () { | |
return (typeof cordova !== "undefined" || | |
typeof PhoneGap !== "undefined" || | |
typeof phonegap !== "undefined"); | |
}; | |
exports.hasLocalStorage = function () { | |
if (isChromeApp()) { | |
return false; | |
} | |
try { | |
return global.localStorage; | |
} catch (e) { | |
return false; | |
} | |
}; | |
exports.Changes = function () { | |
var api = {}; | |
var eventEmitter = new EventEmitter(); | |
var isChrome = isChromeApp(); | |
var listeners = {}; | |
var hasLocal = false; | |
if (!isChrome) { | |
hasLocal = exports.hasLocalStorage(); | |
} | |
if (isChrome) { | |
chrome.storage.onChanged.addListener(function (e) { | |
// make sure it's event addressed to us | |
if (e.db_name != null) { | |
eventEmitter.emit(e.dbName.newValue);//object only has oldValue, newValue members | |
} | |
}); | |
} else if (hasLocal) { | |
global.addEventListener("storage", function (e) { | |
eventEmitter.emit(e.key); | |
}); | |
} | |
api.addListener = function (dbName, id, db, opts) { | |
if (listeners[id]) { | |
return; | |
} | |
function eventFunction() { | |
db.changes({ | |
include_docs: opts.include_docs, | |
conflicts: opts.conflicts, | |
continuous: false, | |
descending: false, | |
filter: opts.filter, | |
view: opts.view, | |
since: opts.since, | |
query_params: opts.query_params, | |
onChange: function (c) { | |
if (c.seq > opts.since && !opts.cancelled) { | |
opts.since = c.seq; | |
exports.call(opts.onChange, c); | |
} | |
} | |
}); | |
} | |
listeners[id] = eventFunction; | |
eventEmitter.on(dbName, eventFunction); | |
}; | |
api.removeListener = function (dbName, id) { | |
if (!(id in listeners)) { | |
return; | |
} | |
eventEmitter.removeListener(dbName, listeners[id]); | |
}; | |
api.clearListeners = function (dbName) { | |
eventEmitter.removeAllListeners(dbName); | |
}; | |
api.notifyLocalWindows = function (dbName) { | |
//do a useless change on a storage thing | |
//in order to get other windows's listeners to activate | |
if (isChrome) { | |
chrome.storage.local.set({dbName: dbName}); | |
} else if (hasLocal) { | |
localStorage[dbName] = (localStorage[dbName] === "a") ? "b" : "a"; | |
} | |
}; | |
api.notify = function (dbName) { | |
eventEmitter.emit(dbName); | |
}; | |
return api; | |
}; | |
if (!process.browser || !('atob' in global)) { | |
exports.atob = function (str) { | |
var base64 = new buffer(str, 'base64'); | |
// Node.js will just skip the characters it can't encode instead of | |
// throwing and exception | |
if (base64.toString('base64') !== str) { | |
throw ("Cannot base64 encode full string"); | |
} | |
return base64.toString('binary'); | |
}; | |
} else { | |
exports.atob = function (str) { | |
return atob(str); | |
}; | |
} | |
if (!process.browser || !('btoa' in global)) { | |
exports.btoa = function (str) { | |
return new buffer(str, 'binary').toString('base64'); | |
}; | |
} else { | |
exports.btoa = function (str) { | |
return btoa(str); | |
}; | |
} | |
// From http://stackoverflow.com/questions/14967647/encode-decode-image-with-base64-breaks-image (2013-04-21) | |
exports.fixBinary = function (bin) { | |
if (!process.browser) { | |
// don't need to do this in Node | |
return 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; | |
}; | |
exports.once = function (fun) { | |
var called = false; | |
return exports.getArguments(function (args) { | |
if (called) { | |
console.trace(); | |
throw new Error('once called more than once'); | |
} else { | |
called = true; | |
fun.apply(this, args); | |
} | |
}); | |
}; | |
exports.toPromise = function (func) { | |
//create the function we will be returning | |
return exports.getArguments(function (args) { | |
var self = this; | |
var tempCB = (typeof args[args.length - 1] === 'function') ? args.pop() : false; | |
// if the last argument is a function, assume its a callback | |
var usedCB; | |
if (tempCB) { | |
// if it was a callback, create a new callback which calls it, | |
// but do so async so we don't trap any errors | |
usedCB = function (err, resp) { | |
process.nextTick(function () { | |
tempCB(err, resp); | |
}); | |
}; | |
} | |
var promise = new Promise(function (fulfill, reject) { | |
try { | |
var callback = exports.once(function (err, mesg) { | |
if (err) { | |
reject(err); | |
} else { | |
fulfill(mesg); | |
} | |
}); | |
// create a callback for this invocation | |
// apply the function in the orig context | |
args.push(callback); | |
func.apply(self, args); | |
} catch (e) { | |
reject(e); | |
} | |
}); | |
// if there is a callback, call it back | |
if (usedCB) { | |
promise.then(function (result) { | |
usedCB(null, result); | |
}, usedCB); | |
} | |
promise.cancel = function () { | |
return this; | |
}; | |
return promise; | |
}); | |
}; | |
exports.adapterFun = function (name, callback) { | |
return exports.toPromise(exports.getArguments(function (args) { | |
if (!this.taskqueue.isReady) { | |
this.taskqueue.addTask(name, args); | |
return; | |
} | |
callback.apply(this, args); | |
})); | |
}; | |
//Can't find original post, but this is close | |
//http://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers | |
exports.arrayBufferToBinaryString = function (buffer) { | |
var binary = ""; | |
var bytes = new Uint8Array(buffer); | |
var length = bytes.byteLength; | |
for (var i = 0; i < length; i++) { | |
binary += String.fromCharCode(bytes[i]); | |
} | |
return binary; | |
}; | |
exports.cancellableFun = function (fun, self, opts) { | |
opts = opts ? exports.extend(true, {}, opts) : {}; | |
opts.complete = opts.complete || function () { }; | |
var complete = exports.once(opts.complete); | |
var promise = new Promise(function (fulfill, reject) { | |
opts.complete = function (err, res) { | |
if (err) { | |
reject(err); | |
} else { | |
fulfill(res); | |
} | |
}; | |
}); | |
promise.then(function (result) { | |
complete(null, result); | |
}, complete); | |
// this needs to be overwridden by caller, dont fire complete until | |
// the task is ready | |
promise.cancel = function () { | |
promise.isCancelled = true; | |
if (self.taskqueue.isReady) { | |
opts.complete(null, {status: 'cancelled'}); | |
} | |
}; | |
if (!self.taskqueue.isReady) { | |
self.taskqueue.addTask(function () { | |
if (promise.isCancelled) { | |
opts.complete(null, {status: 'cancelled'}); | |
} else { | |
fun(self, opts, promise); | |
} | |
}); | |
return promise; | |
} else { | |
fun(self, opts, promise); | |
return promise; | |
} | |
}; | |
}).call(this,_dereq_("/Users/tom/Projects/src/node/pouchdb/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js"),typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{"./deps/ajax":6,"./deps/blob":7,"./deps/buffer":21,"./deps/errors":8,"./deps/extend":10,"./deps/md5.js":11,"./deps/uuid":12,"./merge":14,"/Users/tom/Projects/src/node/pouchdb/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js":23,"bluebird":28,"events":22,"inherits":24}],19:[function(_dereq_,module,exports){ | |
module.exports = _dereq_('../package.json').version; | |
},{"../package.json":53}],20:[function(_dereq_,module,exports){ | |
},{}],21:[function(_dereq_,module,exports){ | |
module.exports=_dereq_(20) | |
},{}],22:[function(_dereq_,module,exports){ | |
// Copyright Joyent, Inc. and other Node contributors. | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a | |
// copy of this software and associated documentation files (the | |
// "Software"), to deal in the Software without restriction, including | |
// without limitation the rights to use, copy, modify, merge, publish, | |
// distribute, sublicense, and/or sell copies of the Software, and to permit | |
// persons to whom the Software is furnished to do so, subject to the | |
// following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included | |
// in all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN | |
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | |
// USE OR OTHER DEALINGS IN THE SOFTWARE. | |
function EventEmitter() { | |
this._events = this._events || {}; | |
this._maxListeners = this._maxListeners || undefined; | |
} | |
module.exports = EventEmitter; | |
// Backwards-compat with node 0.10.x | |
EventEmitter.EventEmitter = EventEmitter; | |
EventEmitter.prototype._events = undefined; | |
EventEmitter.prototype._maxListeners = undefined; | |
// By default EventEmitters will print a warning if more than 10 listeners are | |
// added to it. This is a useful default which helps finding memory leaks. | |
EventEmitter.defaultMaxListeners = 10; | |
// Obviously not all Emitters should be limited to 10. This function allows | |
// that to be increased. Set to zero for unlimited. | |
EventEmitter.prototype.setMaxListeners = function(n) { | |
if (!isNumber(n) || n < 0 || isNaN(n)) | |
throw TypeError('n must be a positive number'); | |
this._maxListeners = n; | |
return this; | |
}; | |
EventEmitter.prototype.emit = function(type) { | |
var er, handler, len, args, i, listeners; | |
if (!this._events) | |
this._events = {}; | |
// If there is no 'error' event listener then throw. | |
if (type === 'error') { | |
if (!this._events.error || | |
(isObject(this._events.error) && !this._events.error.length)) { | |
er = arguments[1]; | |
if (er instanceof Error) { | |
throw er; // Unhandled 'error' event | |
} else { | |
throw TypeError('Uncaught, unspecified "error" event.'); | |
} | |
return false; | |
} | |
} | |
handler = this._events[type]; | |
if (isUndefined(handler)) | |
return false; | |
if (isFunction(handler)) { | |
switch (arguments.length) { | |
// fast cases | |
case 1: | |
handler.call(this); | |
break; | |
case 2: | |
handler.call(this, arguments[1]); | |
break; | |
case 3: | |
handler.call(this, arguments[1], arguments[2]); | |
break; | |
// slower | |
default: | |
len = arguments.length; | |
args = new Array(len - 1); | |
for (i = 1; i < len; i++) | |
args[i - 1] = arguments[i]; | |
handler.apply(this, args); | |
} | |
} else if (isObject(handler)) { | |
len = arguments.length; | |
args = new Array(len - 1); | |
for (i = 1; i < len; i++) | |
args[i - 1] = arguments[i]; | |
listeners = handler.slice(); | |
len = listeners.length; | |
for (i = 0; i < len; i++) | |
listeners[i].apply(this, args); | |
} | |
return true; | |
}; | |
EventEmitter.prototype.addListener = function(type, listener) { | |
var m; | |
if (!isFunction(listener)) | |
throw TypeError('listener must be a function'); | |
if (!this._events) | |
this._events = {}; | |
// To avoid recursion in the case that type === "newListener"! Before | |
// adding it to the listeners, first emit "newListener". | |
if (this._events.newListener) | |
this.emit('newListener', type, | |
isFunction(listener.listener) ? | |
listener.listener : listener); | |
if (!this._events[type]) | |
// Optimize the case of one listener. Don't need the extra array object. | |
this._events[type] = listener; | |
else if (isObject(this._events[type])) | |
// If we've already got an array, just append. | |
this._events[type].push(listener); | |
else | |
// Adding the second element, need to change to array. | |
this._events[type] = [this._events[type], listener]; | |
// Check for listener leak | |
if (isObject(this._events[type]) && !this._events[type].warned) { | |
var m; | |
if (!isUndefined(this._maxListeners)) { | |
m = this._maxListeners; | |
} else { | |
m = EventEmitter.defaultMaxListeners; | |
} | |
if (m && m > 0 && this._events[type].length > m) { | |
this._events[type].warned = true; | |
console.error('(node) warning: possible EventEmitter memory ' + | |
'leak detected. %d listeners added. ' + | |
'Use emitter.setMaxListeners() to increase limit.', | |
this._events[type].length); | |
console.trace(); | |
} | |
} | |
return this; | |
}; | |
EventEmitter.prototype.on = EventEmitter.prototype.addListener; | |
EventEmitter.prototype.once = function(type, listener) { | |
if (!isFunction(listener)) | |
throw TypeError('listener must be a function'); | |
var fired = false; | |
function g() { | |
this.removeListener(type, g); | |
if (!fired) { | |
fired = true; | |
listener.apply(this, arguments); | |
} | |
} | |
g.listener = listener; | |
this.on(type, g); | |
return this; | |
}; | |
// emits a 'removeListener' event iff the listener was removed | |
EventEmitter.prototype.removeListener = function(type, listener) { | |
var list, position, length, i; | |
if (!isFunction(listener)) | |
throw TypeError('listener must be a function'); | |
if (!this._events || !this._events[type]) | |
return this; | |
list = this._events[type]; | |
length = list.length; | |
position = -1; | |
if (list === listener || | |
(isFunction(list.listener) && list.listener === listener)) { | |
delete this._events[type]; | |
if (this._events.removeListener) | |
this.emit('removeListener', type, listener); | |
} else if (isObject(list)) { | |
for (i = length; i-- > 0;) { | |
if (list[i] === listener || | |
(list[i].listener && list[i].listener === listener)) { | |
position = i; | |
break; | |
} | |
} | |
if (position < 0) | |
return this; | |
if (list.length === 1) { | |
list.length = 0; | |
delete this._events[type]; | |
} else { | |
list.splice(position, 1); | |
} | |
if (this._events.removeListener) | |
this.emit('removeListener', type, listener); | |
} | |
return this; | |
}; | |
EventEmitter.prototype.removeAllListeners = function(type) { | |
var key, listeners; | |
if (!this._events) | |
return this; | |
// not listening for removeListener, no need to emit | |
if (!this._events.removeListener) { | |
if (arguments.length === 0) | |
this._events = {}; | |
else if (this._events[type]) | |
delete this._events[type]; | |
return this; | |
} | |
// emit removeListener for all listeners on all events | |
if (arguments.length === 0) { | |
for (key in this._events) { | |
if (key === 'removeListener') continue; | |
this.removeAllListeners(key); | |
} | |
this.removeAllListeners('removeListener'); | |
this._events = {}; | |
return this; | |
} | |
listeners = this._events[type]; | |
if (isFunction(listeners)) { | |
this.removeListener(type, listeners); | |
} else { | |
// LIFO order | |
while (listeners.length) | |
this.removeListener(type, listeners[listeners.length - 1]); | |
} | |
delete this._events[type]; | |
return this; | |
}; | |
EventEmitter.prototype.listeners = function(type) { | |
var ret; | |
if (!this._events || !this._events[type]) | |
ret = []; | |
else if (isFunction(this._events[type])) | |
ret = [this._events[type]]; | |
else | |
ret = this._events[type].slice(); | |
return ret; | |
}; | |
EventEmitter.listenerCount = function(emitter, type) { | |
var ret; | |
if (!emitter._events || !emitter._events[type]) | |
ret = 0; | |
else if (isFunction(emitter._events[type])) | |
ret = 1; | |
else | |
ret = emitter._events[type].length; | |
return ret; | |
}; | |
function isFunction(arg) { | |
return typeof arg === 'function'; | |
} | |
function isNumber(arg) { | |
return typeof arg === 'number'; | |
} | |
function isObject(arg) { | |
return typeof arg === 'object' && arg !== null; | |
} | |
function isUndefined(arg) { | |
return arg === void 0; | |
} | |
},{}],23:[function(_dereq_,module,exports){ | |
// shim for using process in browser | |
var process = module.exports = {}; | |
process.nextTick = (function () { | |
var canSetImmediate = typeof window !== 'undefined' | |
&& window.setImmediate; | |
var canPost = typeof window !== 'undefined' | |
&& window.postMessage && window.addEventListener | |
; | |
if (canSetImmediate) { | |
return function (f) { return window.setImmediate(f) }; | |
} | |
if (canPost) { | |
var queue = []; | |
window.addEventListener('message', function (ev) { | |
var source = ev.source; | |
if ((source === window || source === null) && ev.data === 'process-tick') { | |
ev.stopPropagation(); | |
if (queue.length > 0) { | |
var fn = queue.shift(); | |
fn(); | |
} | |
} | |
}, true); | |
return function nextTick(fn) { | |
queue.push(fn); | |
window.postMessage('process-tick', '*'); | |
}; | |
} | |
return function nextTick(fn) { | |
setTimeout(fn, 0); | |
}; | |
})(); | |
process.title = 'browser'; | |
process.browser = true; | |
process.env = {}; | |
process.argv = []; | |
process.binding = function (name) { | |
throw new Error('process.binding is not supported'); | |
} | |
// TODO(shtylman) | |
process.cwd = function () { return '/' }; | |
process.chdir = function (dir) { | |
throw new Error('process.chdir is not supported'); | |
}; | |
},{}],24:[function(_dereq_,module,exports){ | |
if (typeof Object.create === 'function') { | |
// implementation from standard node.js 'util' module | |
module.exports = function inherits(ctor, superCtor) { | |
ctor.super_ = superCtor | |
ctor.prototype = Object.create(superCtor.prototype, { | |
constructor: { | |
value: ctor, | |
enumerable: false, | |
writable: true, | |
configurable: true | |
} | |
}); | |
}; | |
} else { | |
// old school shim for old browsers | |
module.exports = function inherits(ctor, superCtor) { | |
ctor.super_ = superCtor | |
var TempCtor = function () {} | |
TempCtor.prototype = superCtor.prototype | |
ctor.prototype = new TempCtor() | |
ctor.prototype.constructor = ctor | |
} | |
} | |
},{}],25:[function(_dereq_,module,exports){ | |
'use strict'; | |
module.exports = INTERNAL; | |
function INTERNAL() {} | |
},{}],26:[function(_dereq_,module,exports){ | |
'use strict'; | |
var INTERNAL = _dereq_('./INTERNAL'); | |
var Promise = _dereq_('./promise'); | |
var reject = _dereq_('./reject'); | |
var resolve = _dereq_('./resolve'); | |
module.exports = function all(iterable) { | |
if (Object.prototype.toString.call(iterable) !== '[object Array]') { | |
return reject(new TypeError('must be an array')); | |
} | |
var len = iterable.length; | |
if (!len) { | |
return resolve([]); | |
} | |
var values = []; | |
var resolved = 0; | |
var i = -1; | |
var promise = new Promise(INTERNAL); | |
function allResolver(value, i) { | |
resolve(value).then(function (outValue) { | |
values[i] = outValue; | |
if (++resolved === len) { | |
promise.resolve(values); | |
} | |
}, function (error) { | |
promise.reject(error); | |
}); | |
} | |
while (++i < len) { | |
allResolver(iterable[i], i); | |
} | |
return promise; | |
}; | |
},{"./INTERNAL":25,"./promise":30,"./reject":31,"./resolve":32}],27:[function(_dereq_,module,exports){ | |
'use strict'; | |
module.exports = getThen; | |
function getThen(obj) { | |
// Make sure we only access the accessor once as required by the spec | |
var then = obj && obj.then; | |
if (obj && typeof obj === 'object' && typeof then === 'function') { | |
return function appyThen() { | |
then.apply(obj, arguments); | |
}; | |
} | |
} | |
},{}],28:[function(_dereq_,module,exports){ | |
module.exports = exports = _dereq_('./promise'); | |
exports.resolve = _dereq_('./resolve'); | |
exports.reject = _dereq_('./reject'); | |
exports.all = _dereq_('./all'); | |
},{"./all":26,"./promise":30,"./reject":31,"./resolve":32}],29:[function(_dereq_,module,exports){ | |
'use strict'; | |
module.exports = once; | |
/* Wrap an arbitrary number of functions and allow only one of them to be | |
executed and only once */ | |
function once() { | |
var called = 0; | |
return function wrapper(wrappedFunction) { | |
return function () { | |
if (called++) { | |
return; | |
} | |
wrappedFunction.apply(this, arguments); | |
}; | |
}; | |
} | |
},{}],30:[function(_dereq_,module,exports){ | |
'use strict'; | |
var unwrap = _dereq_('./unwrap'); | |
var INTERNAL = _dereq_('./INTERNAL'); | |
var once = _dereq_('./once'); | |
var tryCatch = _dereq_('./tryCatch'); | |
var getThen = _dereq_('./getThen'); | |
// Lazy man's symbols for states | |
var PENDING = ['PENDING'], | |
FULFILLED = ['FULFILLED'], | |
REJECTED = ['REJECTED']; | |
module.exports = Promise; | |
function Promise(resolver) { | |
if (!(this instanceof Promise)) { | |
return new Promise(resolver); | |
} | |
if (typeof resolver !== 'function') { | |
throw new TypeError('reslover must be a function'); | |
} | |
this.state = PENDING; | |
this.queue = []; | |
if (resolver !== INTERNAL) { | |
safelyResolveThenable(this, resolver); | |
} | |
} | |
Promise.prototype.resolve = function (value) { | |
var result = tryCatch(getThen, value); | |
if (result.status === 'error') { | |
return this.reject(result.value); | |
} | |
var thenable = result.value; | |
if (thenable) { | |
safelyResolveThenable(this, thenable); | |
} else { | |
this.state = FULFILLED; | |
this.outcome = value; | |
var i = -1; | |
var len = this.queue.length; | |
while (++i < len) { | |
this.queue[i].callFulfilled(value); | |
} | |
} | |
return this; | |
}; | |
Promise.prototype.reject = function (error) { | |
this.state = REJECTED; | |
this.outcome = error; | |
var i = -1; | |
var len = this.queue.length; | |
while (++i < len) { | |
this.queue[i].callRejected(error); | |
} | |
return this; | |
}; | |
Promise.prototype['catch'] = function (onRejected) { | |
return this.then(null, onRejected); | |
}; | |
Promise.prototype.then = function (onFulfilled, onRejected) { | |
var onFulfilledFunc = typeof onFulfilled === 'function'; | |
var onRejectedFunc = typeof onRejected === 'function'; | |
if (!onFulfilledFunc && this.state === FULFILLED || !onRejected && this.state === REJECTED) { | |
return this; | |
} | |
var promise = new Promise(INTERNAL); | |
var thenHandler = { | |
promise: promise, | |
}; | |
if (this.state !== REJECTED) { | |
if (onFulfilledFunc) { | |
thenHandler.callFulfilled = function (value) { | |
unwrap(promise, onFulfilled, value); | |
}; | |
} else { | |
thenHandler.callFulfilled = function (value) { | |
promise.resolve(value); | |
}; | |
} | |
} | |
if (this.state !== FULFILLED) { | |
if (onRejectedFunc) { | |
thenHandler.callRejected = function (value) { | |
unwrap(promise, onRejected, value); | |
}; | |
} else { | |
thenHandler.callRejected = function (value) { | |
promise.reject(value); | |
}; | |
} | |
} | |
if (this.state === FULFILLED) { | |
thenHandler.callFulfilled(this.outcome); | |
} else if (this.state === REJECTED) { | |
thenHandler.callRejected(this.outcome); | |
} else { | |
this.queue.push(thenHandler); | |
} | |
return promise; | |
}; | |
function safelyResolveThenable(self, thenable) { | |
// Either fulfill, reject or reject with error | |
var onceWrapper = once(); | |
var onError = onceWrapper(function (value) { | |
return self.reject(value); | |
}); | |
var result = tryCatch(function () { | |
thenable( | |
onceWrapper(function (value) { | |
return self.resolve(value); | |
}), | |
onError | |
); | |
}); | |
if (result.status === 'error') { | |
onError(result.value); | |
} | |
} | |
},{"./INTERNAL":25,"./getThen":27,"./once":29,"./tryCatch":33,"./unwrap":34}],31:[function(_dereq_,module,exports){ | |
'use strict'; | |
var Promise = _dereq_('./promise'); | |
var INTERNAL = _dereq_('./INTERNAL'); | |
module.exports = reject; | |
function reject(reason) { | |
var promise = new Promise(INTERNAL); | |
return promise.reject(reason); | |
} | |
},{"./INTERNAL":25,"./promise":30}],32:[function(_dereq_,module,exports){ | |
'use strict'; | |
var Promise = _dereq_('./promise'); | |
var INTERNAL = _dereq_('./INTERNAL'); | |
module.exports = resolve; | |
var FALSE = new Promise(INTERNAL).resolve(false); | |
var NULL = new Promise(INTERNAL).resolve(null); | |
var UNDEFINED = new Promise(INTERNAL).resolve(void 0); | |
var ZERO = new Promise(INTERNAL).resolve(0); | |
var EMPTYSTRING = new Promise(INTERNAL).resolve(''); | |
function resolve(value) { | |
if (value) { | |
return new Promise(INTERNAL).resolve(value); | |
} | |
var valueType = typeof value; | |
switch (valueType) { | |
case 'boolean': | |
return FALSE; | |
case 'undefined': | |
return UNDEFINED; | |
case 'object': | |
return NULL; | |
case 'number': | |
return ZERO; | |
case 'string': | |
return EMPTYSTRING; | |
} | |
} | |
},{"./INTERNAL":25,"./promise":30}],33:[function(_dereq_,module,exports){ | |
'use strict'; | |
module.exports = tryCatch; | |
function tryCatch(func, value) { | |
var out = {}; | |
try { | |
out.value = func(value); | |
out.status = 'success'; | |
} catch (e) { | |
out.status = 'error'; | |
out.value = e; | |
} | |
return out; | |
} | |
},{}],34:[function(_dereq_,module,exports){ | |
'use strict'; | |
var immediate = _dereq_('immediate'); | |
module.exports = unwrap; | |
function unwrap(promise, func, value) { | |
immediate(function () { | |
var returnValue; | |
try { | |
returnValue = func(value); | |
} catch (e) { | |
return promise.reject(e); | |
} | |
if (returnValue === promise) { | |
promise.reject(new TypeError('Cannot resolve promise with itself')); | |
} else { | |
promise.resolve(returnValue); | |
} | |
}); | |
} | |
},{"immediate":36}],35:[function(_dereq_,module,exports){ | |
"use strict"; | |
exports.test = function () { | |
return false; | |
}; | |
},{}],36:[function(_dereq_,module,exports){ | |
"use strict"; | |
var types = [ | |
_dereq_("./nextTick"), | |
_dereq_("./mutation"), | |
_dereq_("./postMessage"), | |
_dereq_("./messageChannel"), | |
_dereq_("./stateChange"), | |
_dereq_("./timeout") | |
]; | |
var handlerQueue = []; | |
function drainQueue() { | |
var i = 0, | |
task, | |
innerQueue = handlerQueue; | |
handlerQueue = []; | |
/*jslint boss: true */ | |
while (task = innerQueue[i++]) { | |
task(); | |
} | |
} | |
var nextTick; | |
var i = -1; | |
var len = types.length; | |
while (++ i < len) { | |
if (types[i].test()) { | |
nextTick = types[i].install(drainQueue); | |
break; | |
} | |
} | |
module.exports = function (task) { | |
var len, i, args; | |
var nTask = task; | |
if (arguments.length > 1 && typeof task === "function") { | |
args = new Array(arguments.length - 1); | |
i = 0; | |
while (++i < arguments.length) { | |
args[i - 1] = arguments[i]; | |
} | |
nTask = function () { | |
task.apply(undefined, args); | |
}; | |
} | |
if ((len = handlerQueue.push(nTask)) === 1) { | |
nextTick(drainQueue); | |
} | |
return len; | |
}; | |
module.exports.clear = function (n) { | |
if (n <= handlerQueue.length) { | |
handlerQueue[n - 1] = function () {}; | |
} | |
return this; | |
}; | |
},{"./messageChannel":37,"./mutation":38,"./nextTick":35,"./postMessage":39,"./stateChange":40,"./timeout":41}],37:[function(_dereq_,module,exports){ | |
(function (global){ | |
"use strict"; | |
exports.test = function () { | |
return typeof global.MessageChannel !== "undefined"; | |
}; | |
exports.install = function (func) { | |
var channel = new global.MessageChannel(); | |
channel.port1.onmessage = func; | |
return function () { | |
channel.port2.postMessage(0); | |
}; | |
}; | |
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{}],38:[function(_dereq_,module,exports){ | |
(function (global){ | |
"use strict"; | |
//based off rsvp | |
//https://github.com/tildeio/rsvp.js/blob/master/lib/rsvp/async.js | |
var MutationObserver = global.MutationObserver || global.WebKitMutationObserver; | |
exports.test = function () { | |
return MutationObserver; | |
}; | |
exports.install = function (handle) { | |
var observer = new MutationObserver(handle); | |
var element = global.document.createElement("div"); | |
observer.observe(element, { attributes: true }); | |
// Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661 | |
global.addEventListener("unload", function () { | |
observer.disconnect(); | |
observer = null; | |
}, false); | |
return function () { | |
element.setAttribute("drainQueue", "drainQueue"); | |
}; | |
}; | |
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{}],39:[function(_dereq_,module,exports){ | |
(function (global){ | |
"use strict"; | |
exports.test = function () { | |
// The test against `importScripts` prevents this implementation from being installed inside a web worker, | |
// where `global.postMessage` means something completely different and can"t be used for this purpose. | |
if (!global.postMessage || global.importScripts) { | |
return false; | |
} | |
var postMessageIsAsynchronous = true; | |
var oldOnMessage = global.onmessage; | |
global.onmessage = function () { | |
postMessageIsAsynchronous = false; | |
}; | |
global.postMessage("", "*"); | |
global.onmessage = oldOnMessage; | |
return postMessageIsAsynchronous; | |
}; | |
exports.install = function (func) { | |
var codeWord = "com.calvinmetcalf.setImmediate" + Math.random(); | |
function globalMessage(event) { | |
if (event.source === global && event.data === codeWord) { | |
func(); | |
} | |
} | |
if (global.addEventListener) { | |
global.addEventListener("message", globalMessage, false); | |
} else { | |
global.attachEvent("onmessage", globalMessage); | |
} | |
return function () { | |
global.postMessage(codeWord, "*"); | |
}; | |
}; | |
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{}],40:[function(_dereq_,module,exports){ | |
(function (global){ | |
"use strict"; | |
exports.test = function () { | |
return "document" in global && "onreadystatechange" in global.document.createElement("script"); | |
}; | |
exports.install = function (handle) { | |
return function () { | |
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted | |
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called. | |
var scriptEl = global.document.createElement("script"); | |
scriptEl.onreadystatechange = function () { | |
handle(); | |
scriptEl.onreadystatechange = null; | |
scriptEl.parentNode.removeChild(scriptEl); | |
scriptEl = null; | |
}; | |
global.document.documentElement.appendChild(scriptEl); | |
return handle; | |
}; | |
}; | |
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{}],41:[function(_dereq_,module,exports){ | |
"use strict"; | |
exports.test = function () { | |
return true; | |
}; | |
exports.install = function (t) { | |
return function () { | |
setTimeout(t, 0); | |
}; | |
}; | |
},{}],42:[function(_dereq_,module,exports){ | |
'use strict'; | |
module.exports = function (func, emit, sum, log, isArray, toJSON) { | |
/*jshint evil: true */ | |
return eval("'use strict'; (" + func + ");"); | |
}; | |
},{}],43:[function(_dereq_,module,exports){ | |
(function (process,global){ | |
'use strict'; | |
var pouchCollate = _dereq_('pouchdb-collate'); | |
var Promise = typeof global.Promise === 'function' ? global.Promise : _dereq_('lie'); | |
var collate = pouchCollate.collate; | |
var evalFunc = _dereq_('./evalfunc'); | |
var log = (typeof console !== 'undefined') ? | |
Function.prototype.bind.call(console.log, console) : function () {}; | |
var processKey = function (key) { | |
// Stringify keys since we want them as map keys (see #35) | |
return JSON.stringify(pouchCollate.normalizeKey(key)); | |
}; | |
// This is the first implementation of a basic plugin, we register the | |
// plugin object with pouch and it is mixin'd to each database created | |
// (regardless of adapter), adapters can override plugins by providing | |
// their own implementation. functions on the plugin object that start | |
// with _ are reserved function that are called by pouchdb for special | |
// notifications. | |
// If we wanted to store incremental views we can do it here by listening | |
// to the changes feed (keeping track of our last update_seq between page loads) | |
// and storing the result of the map function (possibly using the upcoming | |
// extracted adapter functions) | |
function createKeysLookup(keys) { | |
// creates a lookup map for the given keys, so that doing | |
// query() with keys doesn't become an O(n * m) operation | |
// lookup values are typically integer indexes, but may | |
// map to a list of integers, since keys can be duplicated | |
var lookup = {}; | |
for (var i = 0, len = keys.length; i < len; i++) { | |
var key = processKey(keys[i]); | |
var val = lookup[key]; | |
if (typeof val === 'undefined') { | |
lookup[key] = i; | |
} else if (typeof val === 'number') { | |
lookup[key] = [val, i]; | |
} else { // array | |
val.push(i); | |
} | |
} | |
return lookup; | |
} | |
function sortByIdAndValue(a, b) { | |
// sort by id, then value | |
var idCompare = collate(a.id, b.id); | |
return idCompare !== 0 ? idCompare : collate(a.value, b.value); | |
} | |
function addAtIndex(idx, result, prelimResults) { | |
var val = prelimResults[idx]; | |
if (typeof val === 'undefined') { | |
prelimResults[idx] = result; | |
} else if (!Array.isArray(val)) { | |
// same key for multiple docs, need to preserve document order, so create array | |
prelimResults[idx] = [val, result]; | |
} else { // existing array | |
val.push(result); | |
} | |
} | |
function sum(values) { | |
return values.reduce(function (a, b) { | |
return a + b; | |
}, 0); | |
} | |
var builtInReduce = { | |
"_sum": function (keys, values) { | |
return sum(values); | |
}, | |
"_count": function (keys, values, rereduce) { | |
return values.length; | |
}, | |
"_stats": function (keys, values) { | |
return { | |
'sum': sum(values), | |
'min': Math.min.apply(null, values), | |
'max': Math.max.apply(null, values), | |
'count': values.length, | |
'sumsqr': (function () { | |
var _sumsqr = 0; | |
var error; | |
for (var idx in values) { | |
if (typeof values[idx] === 'number') { | |
_sumsqr += values[idx] * values[idx]; | |
} else { | |
error = new Error('builtin _stats function requires map values to be numbers'); | |
error.name = 'invalid_value'; | |
error.status = 500; | |
return error; | |
} | |
} | |
return _sumsqr; | |
})() | |
}; | |
} | |
}; | |
function addHttpParam(paramName, opts, params, asJson) { | |
// add an http param from opts to params, optionally json-encoded | |
var val = opts[paramName]; | |
if (typeof val !== 'undefined') { | |
if (asJson) { | |
val = encodeURIComponent(JSON.stringify(val)); | |
} | |
params.push(paramName + '=' + val); | |
} | |
} | |
function mapUsingKeys(inputResults, keys, keysLookup) { | |
// create a new results array from the given array, | |
// ensuring that the following conditions are respected: | |
// 1. docs are ordered by key, then doc id | |
// 2. docs can appear >1 time in the list, if their key is specified >1 time | |
// 3. keys can be unknown, in which case there's just a hole in the returned array | |
var prelimResults = new Array(keys.length); | |
inputResults.forEach(function (result) { | |
var idx = keysLookup[processKey(result.key)]; | |
if (typeof idx === 'number') { | |
addAtIndex(idx, result, prelimResults); | |
} else { // array of indices | |
idx.forEach(function (subIdx) { | |
addAtIndex(subIdx, result, prelimResults); | |
}); | |
} | |
}); | |
// flatten the array, remove nulls, sort by doc ids | |
var outputResults = []; | |
prelimResults.forEach(function (result) { | |
if (Array.isArray(result)) { | |
outputResults = outputResults.concat(result.sort(sortByIdAndValue)); | |
} else { // single result | |
outputResults.push(result); | |
} | |
}); | |
return outputResults; | |
} | |
function viewQuery(db, fun, options) { | |
var origMap; | |
if (!options.skip) { | |
options.skip = 0; | |
} | |
if (!fun.reduce) { | |
options.reduce = false; | |
} | |
var results = []; | |
var current; | |
var num_started = 0; | |
var completed = false; | |
var keysLookup; | |
function emit(key, val) { | |
var viewRow = { | |
id: current.doc._id, | |
key: key, | |
value: val | |
}; | |
if (typeof options.startkey !== 'undefined' && collate(key, options.startkey) < 0) { | |
return; | |
} | |
if (typeof options.endkey !== 'undefined' && collate(key, options.endkey) > 0) { | |
return; | |
} | |
if (typeof options.key !== 'undefined' && collate(key, options.key) !== 0) { | |
return; | |
} | |
if (typeof options.keys !== 'undefined') { | |
keysLookup = keysLookup || createKeysLookup(options.keys); | |
if (typeof keysLookup[processKey(key)] === 'undefined') { | |
return; | |
} | |
} | |
num_started++; | |
if (options.include_docs) { | |
//in this special case, join on _id (issue #106) | |
if (val && typeof val === 'object' && val._id) { | |
db.get(val._id, | |
function (_, joined_doc) { | |
if (joined_doc) { | |
viewRow.doc = joined_doc; | |
} | |
results.push(viewRow); | |
checkComplete(); | |
}); | |
return; | |
} else { | |
viewRow.doc = current.doc; | |
} | |
} | |
results.push(viewRow); | |
} | |
if (typeof fun.map === "function" && fun.map.length === 2) { | |
//save a reference to it | |
origMap = fun.map; | |
fun.map = function (doc) { | |
//call it with the emit as the second argument | |
return origMap(doc, emit); | |
}; | |
} else { | |
// ugly way to make sure references to 'emit' in map/reduce bind to the | |
// above emit | |
fun.map = evalFunc(fun.map.toString(), emit, sum, log, Array.isArray, JSON.parse); | |
} | |
if (fun.reduce) { | |
if (builtInReduce[fun.reduce]) { | |
fun.reduce = builtInReduce[fun.reduce]; | |
} else { | |
fun.reduce = evalFunc(fun.reduce.toString(), emit, sum, log, Array.isArray, JSON.parse); | |
} | |
} | |
//only proceed once all documents are mapped and joined | |
function checkComplete() { | |
var error; | |
if (completed && results.length === num_started) { | |
if (typeof options.keys !== 'undefined' && results.length) { | |
// user supplied a keys param, sort by keys | |
results = mapUsingKeys(results, options.keys, keysLookup); | |
} else { // normal sorting | |
results.sort(function (a, b) { | |
// sort by key, then id | |
var keyCollate = collate(a.key, b.key); | |
return keyCollate !== 0 ? keyCollate : collate(a.id, b.id); | |
}); | |
} | |
if (options.descending) { | |
results.reverse(); | |
} | |
if (options.reduce === false) { | |
return options.complete(null, { | |
total_rows: results.length, | |
offset: options.skip, | |
rows: ('limit' in options) ? results.slice(options.skip, options.limit + options.skip) : | |
(options.skip > 0) ? results.slice(options.skip) : results | |
}); | |
} | |
var groups = []; | |
results.forEach(function (e) { | |
var last = groups[groups.length - 1]; | |
if (last && collate(last.key[0][0], e.key) === 0) { | |
last.key.push([e.key, e.id]); | |
last.value.push(e.value); | |
return; | |
} | |
groups.push({key: [ | |
[e.key, e.id] | |
], value: [e.value]}); | |
}); | |
groups.forEach(function (e) { | |
e.value = fun.reduce.call(null, e.key, e.value); | |
if (e.value.sumsqr && e.value.sumsqr instanceof Error) { | |
error = e.value; | |
return; | |
} | |
e.key = e.key[0][0]; | |
}); | |
if (error) { | |
options.complete(error); | |
return; | |
} | |
options.complete(null, { | |
total_rows: groups.length, | |
offset: options.skip, | |
rows: ('limit' in options) ? groups.slice(options.skip, options.limit + options.skip) : | |
(options.skip > 0) ? groups.slice(options.skip) : groups | |
}); | |
} | |
} | |
db.changes({ | |
conflicts: true, | |
include_docs: true, | |
onChange: function (doc) { | |
if (!('deleted' in doc) && doc.id[0] !== "_") { | |
current = {doc: doc.doc}; | |
fun.map.call(null, doc.doc); | |
} | |
}, | |
complete: function () { | |
completed = true; | |
checkComplete(); | |
} | |
}); | |
} | |
function httpQuery(db, fun, opts) { | |
var callback = opts.complete; | |
// List of parameters to add to the PUT request | |
var params = []; | |
var body; | |
var method = 'GET'; | |
// If opts.reduce exists and is defined, then add it to the list | |
// of parameters. | |
// If reduce=false then the results are that of only the map function | |
// not the final result of map and reduce. | |
addHttpParam('reduce', opts, params); | |
addHttpParam('include_docs', opts, params); | |
addHttpParam('limit', opts, params); | |
addHttpParam('descending', opts, params); | |
addHttpParam('group', opts, params); | |
addHttpParam('group_level', opts, params); | |
addHttpParam('skip', opts, params); | |
addHttpParam('startkey', opts, params, true); | |
addHttpParam('endkey', opts, params, true); | |
addHttpParam('key', opts, params, true); | |
// If keys are supplied, issue a POST request to circumvent GET query string limits | |
// see http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options | |
if (typeof opts.keys !== 'undefined') { | |
method = 'POST'; | |
if (typeof fun === 'string') { | |
body = JSON.stringify({keys: opts.keys}); | |
} else { // fun is {map : mapfun}, so append to this | |
fun.keys = opts.keys; | |
} | |
} | |
// Format the list of parameters into a valid URI query string | |
params = params.join('&'); | |
params = params === '' ? '' : '?' + params; | |
// We are referencing a query defined in the design doc | |
if (typeof fun === 'string') { | |
var parts = fun.split('/'); | |
db.request({ | |
method: method, | |
url: '_design/' + parts[0] + '/_view/' + parts[1] + params, | |
body: body | |
}, callback); | |
return; | |
} | |
// We are using a temporary view, terrible for performance but good for testing | |
var queryObject = JSON.parse(JSON.stringify(fun, function (key, val) { | |
if (typeof val === 'function') { | |
return val + ''; // implicitly `toString` it | |
} | |
return val; | |
})); | |
db.request({ | |
method: 'POST', | |
url: '_temp_view' + params, | |
body: queryObject | |
}, callback); | |
} | |
exports.query = function (fun, opts, callback) { | |
var db = this; | |
if (typeof opts === 'function') { | |
callback = opts; | |
opts = {}; | |
} | |
opts = opts || {}; | |
if (callback) { | |
opts.complete = callback; | |
} | |
var tempCB = opts.complete; | |
var realCB; | |
if (opts.complete) { | |
realCB = function (err, resp) { | |
process.nextTick(function () { | |
tempCB(err, resp); | |
}); | |
}; | |
} | |
var promise = new Promise(function (resolve, reject) { | |
opts.complete = function (err, data) { | |
if (err) { | |
reject(err); | |
} else { | |
resolve(data); | |
} | |
}; | |
if (db.type() === 'http') { | |
if (typeof fun === 'function') { | |
return httpQuery(db, {map: fun}, opts); | |
} | |
return httpQuery(db, fun, opts); | |
} | |
if (typeof fun === 'object') { | |
return viewQuery(db, fun, opts); | |
} | |
if (typeof fun === 'function') { | |
return viewQuery(db, {map: fun}, opts); | |
} | |
var parts = fun.split('/'); | |
db.get('_design/' + parts[0], function (err, doc) { | |
if (err) { | |
opts.complete(err); | |
return; | |
} | |
if (!doc.views[parts[1]]) { | |
opts.complete({ name: 'not_found', message: 'missing_named_view' }); | |
return; | |
} | |
viewQuery(db, { | |
map: doc.views[parts[1]].map, | |
reduce: doc.views[parts[1]].reduce | |
}, opts); | |
}); | |
}); | |
if (realCB) { | |
promise.then(function (resp) { | |
realCB(null, resp); | |
}, realCB); | |
} | |
return promise; | |
}; | |
}).call(this,_dereq_("/Users/tom/Projects/src/node/pouchdb/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js"),typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) | |
},{"./evalfunc":42,"/Users/tom/Projects/src/node/pouchdb/node_modules/browserify/node_modules/insert-module-globals/node_modules/process/browser.js":23,"lie":44,"pouchdb-collate":52}],44:[function(_dereq_,module,exports){ | |
'use strict'; | |
var immediate = _dereq_('immediate'); | |
var isDefineProp = false; | |
// prevents deoptimization | |
(function(){ | |
try { | |
Object.defineProperty({}, 'test', {value:true}); | |
isDefineProp = true; | |
}catch(e){} | |
}()); | |
function defineNonEnum(obj, name, value){ | |
if(isDefineProp){ | |
Object.defineProperty(obj, name, { | |
value: value, | |
configurable: true, | |
writable: true | |
}); | |
}else{ | |
obj[name] = value; | |
} | |
} | |
function Promise(resolver) { | |
if (!(this instanceof Promise)) { | |
return new Promise(resolver); | |
} | |
defineNonEnum(this, 'successQueue', []); | |
defineNonEnum(this, 'failureQueue', []); | |
defineNonEnum(this, 'resolved', false); | |
if(typeof resolver === 'function'){ | |
this.resolvePassed(resolver); | |
} | |
} | |
defineNonEnum(Promise.prototype, 'resolvePassed', function(resolver){ | |
try{ | |
resolver(this.fulfillUnwrap.bind(this),this.reject.bind(this)); | |
}catch(e){ | |
this.reject(e); | |
} | |
}); | |
defineNonEnum(Promise.prototype, 'reject', function(reason){ | |
this.resolve(false,reason); | |
}); | |
defineNonEnum(Promise.prototype, 'fulfill', function(value){ | |
this.resolve(true,value); | |
}); | |
defineNonEnum(Promise.prototype, 'fulfillUnwrap', function(value){ | |
unwrap(this.fulfill.bind(this), this.reject.bind(this), value); | |
}); | |
Promise.prototype.then = function(onFulfilled, onRejected) { | |
if(this.resolved){ | |
return this.resolved(onFulfilled, onRejected); | |
} else { | |
return this.pending(onFulfilled, onRejected); | |
} | |
}; | |
(function(){ | |
try { | |
Promise.prototype['catch'] = function(onRejected) { | |
return this.then(null, onRejected); | |
}; | |
} catch(e){} | |
}()); | |
defineNonEnum(Promise.prototype, 'pending', function(onFulfilled, onRejected){ | |
var self = this; | |
return new Promise(function(success,failure){ | |
if(typeof onFulfilled === 'function'){ | |
self.successQueue.push({ | |
resolve: success, | |
reject: failure, | |
callback:onFulfilled | |
}); | |
}else{ | |
self.successQueue.push({ | |
next: success, | |
callback:false | |
}); | |
} | |
if(typeof onRejected === 'function'){ | |
self.failureQueue.push({ | |
resolve: success, | |
reject: failure, | |
callback:onRejected | |
}); | |
}else{ | |
self.failureQueue.push({ | |
next: failure, | |
callback:false | |
}); | |
} | |
}); | |
}); | |
defineNonEnum(Promise.prototype, 'resolve', function (success, value){ | |
if(this.resolved){ | |
return; | |
} | |
this.resolved = createResolved(this, value, success?0:1); | |
var queue = success ? this.successQueue : this.failureQueue; | |
var len = queue.length; | |
var i = -1; | |
while(++i < len) { | |
if (queue[i].callback) { | |
immediate(execute,queue[i].callback, value, queue[i].resolve, queue[i].reject); | |
}else { | |
queue[i].next(value); | |
} | |
} | |
}); | |
function unwrap(fulfill, reject, value){ | |
if(value && typeof value.then==='function'){ | |
value.then(fulfill,reject); | |
}else{ | |
fulfill(value); | |
} | |
} | |
function createResolved(scope, value, whichArg) { | |
function resolved() { | |
var callback = arguments[whichArg]; | |
if (typeof callback !== 'function') { | |
return scope; | |
}else{ | |
return new Promise(function(resolve,reject){ | |
immediate(execute,callback,value,resolve,reject); | |
}); | |
} | |
} | |
return resolved; | |
} | |
function execute(callback, value, resolve, reject) { | |
try { | |
unwrap(resolve,reject,callback(value)); | |
} catch (error) { | |
reject(error); | |
} | |
} | |
module.exports = Promise; | |
},{"immediate":46}],45:[function(_dereq_,module,exports){ | |
module.exports=_dereq_(35) | |
},{}],46:[function(_dereq_,module,exports){ | |
module.exports=_dereq_(36) | |
},{"./messageChannel":47,"./mutation":48,"./nextTick":45,"./postMessage":49,"./stateChange":50,"./timeout":51}],47:[function(_dereq_,module,exports){ | |
module.exports=_dereq_(37) | |
},{}],48:[function(_dereq_,module,exports){ | |
module.exports=_dereq_(38) | |
},{}],49:[function(_dereq_,module,exports){ | |
module.exports=_dereq_(39) | |
},{}],50:[function(_dereq_,module,exports){ | |
module.exports=_dereq_(40) | |
},{}],51:[function(_dereq_,module,exports){ | |
module.exports=_dereq_(41) | |
},{}],52:[function(_dereq_,module,exports){ | |
'use strict'; | |
exports.collate = function (a, b) { | |
a = exports.normalizeKey(a); | |
b = exports.normalizeKey(b); | |
var ai = collationIndex(a); | |
var bi = collationIndex(b); | |
if ((ai - bi) !== 0) { | |
return ai - bi; | |
} | |
if (a === null) { | |
return 0; | |
} | |
if (typeof a === 'number') { | |
return a - b; | |
} | |
if (typeof a === 'boolean') { | |
return a === b ? 0 : (a < b ? -1 : 1); | |
} | |
if (typeof a === 'string') { | |
return stringCollate(a, b); | |
} | |
if (Array.isArray(a)) { | |
return arrayCollate(a, b); | |
} | |
if (typeof a === 'object') { | |
return objectCollate(a, b); | |
} | |
} | |
// couch considers null/NaN/Infinity/-Infinity === undefined, | |
// for the purposes of mapreduce indexes. also, dates get stringified. | |
exports.normalizeKey = function (key) { | |
if (typeof key === 'undefined') { | |
return null; | |
} else if (typeof key === 'number') { | |
if (key === Infinity || key === -Infinity || isNaN(key)) { | |
return null; | |
} | |
} else if (key instanceof Date) { | |
return key.toJSON(); | |
} | |
return key; | |
} | |
function arrayCollate(a, b) { | |
var len = Math.min(a.length, b.length); | |
for (var i = 0; i < len; i++) { | |
var sort = exports.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 = exports.collate(ak[i], bk[i]); | |
if (sort !== 0) { | |
return sort; | |
} | |
// if the keys are equal sort the values | |
sort = exports.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']; | |
if (id.indexOf(typeof x) !== -1) { | |
if (x === null) { | |
return 1; | |
} | |
return id.indexOf(typeof x) + 2; | |
} | |
if (Array.isArray(x)) { | |
return 4.5; | |
} | |
} | |
},{}],53:[function(_dereq_,module,exports){ | |
module.exports={ | |
"name": "pouchdb", | |
"version": "2.0.2-alpha", | |
"description": "PouchDB is a pocket-sized database.", | |
"release": "nightly", | |
"main": "./lib/index.js", | |
"homepage": "https://github.com/daleharvey/pouchdb", | |
"repository": "https://github.com/daleharvey/pouchdb", | |
"keywords": [ | |
"db", | |
"couchdb", | |
"pouchdb" | |
], | |
"tags": [ | |
"db", | |
"couchdb", | |
"pouchdb" | |
], | |
"dependencies": { | |
"bluebird": "~1.0.0", | |
"inherits": "~2.0.1", | |
"level-js": "~2.0.0", | |
"level-sublevel": "~5.2.0", | |
"leveldown": "~0.10.2", | |
"levelup": "~0.18.2", | |
"lie": "^2.6.0", | |
"pouchdb-mapreduce": "1.0.0", | |
"request": "~2.28.0" | |
}, | |
"devDependencies": { | |
"commander": "~2.1.0", | |
"watchify": "~0.4.1", | |
"uglify-js": "~2.4.6", | |
"jshint": "~2.3.0", | |
"http-proxy": "~0.10.3", | |
"corsproxy": "~0.2.13", | |
"http-server": "~0.5.5", | |
"browserify": "~3.24.13", | |
"wd": "~0.2.8", | |
"tin": "~0.4.0", | |
"mocha": "~1.17.1", | |
"chai": "~1.9.0", | |
"istanbul": "~0.2.4", | |
"ncp": "~0.5.0", | |
"sauce-connect-launcher": "0.2.2", | |
"less": "~1.7.0", | |
"bower": "~1.2.8" | |
}, | |
"scripts": { | |
"jshint": "jshint -c .jshintrc bin/ lib/ tests/*.js", | |
"build-js": "./bin/build-js.sh", | |
"build": "mkdir -p dist && npm run build-js", | |
"test-node": "./bin/run-mocha.sh", | |
"test-browser": "mkdir -p dist && npm run build-js && ./bin/test-browser.js", | |
"dev": "./bin/dev-server.js", | |
"test": "npm run jshint && ./bin/run-test.sh", | |
"publish": "./bin/publish.sh", | |
"publish-site": "./bin/publish-site.sh", | |
"build-site": "./bin/build-site.sh", | |
"shell": "./bin/repl.js" | |
}, | |
"browser": { | |
"./deps/buffer": false, | |
"request": false, | |
"leveldown": "level-js", | |
"bluebird": "lie" | |
} | |
} | |
},{}]},{},[13]) | |
(13) | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment