Skip to content

Instantly share code, notes, and snippets.

@slaskis
Created May 20, 2016 09:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slaskis/93ff78e4286757f742ec8bddc1c337d3 to your computer and use it in GitHub Desktop.
Save slaskis/93ff78e4286757f742ec8bddc1c337d3 to your computer and use it in GitHub Desktop.
A PouchDB-like HTTP interface which allows setting defaults and a `baseUrl` for easy CouchDB.
'use strict';
var debug = require('debug')('poachdb');
var http = require('request');
var merge = require('deep-extend');
module.exports = PoachDB;
function PoachDB(name, options) {
debug('PoachDB(%s, %j)', name, options);
this.name = name;
this.options = options || {};
}
PoachDB.prototype.get = function get(key, options) {
return this.request({
uri: '/' + key,
qs: options || {}
});
};
PoachDB.prototype.put = function put(key, doc, options) {
if (typeof key === 'object') {
options = doc;
doc = key;
key = doc._id;
}
return this.request({
method: 'put',
uri: '/' + key,
body: doc,
qs: options || {}
});
};
PoachDB.prototype.post = function post(doc, options) {
return this.put(uuid(), doc, options);
};
PoachDB.prototype.remove = function remove(key, doc, options) {
if (typeof key === 'object') {
options = doc;
doc = key;
key = doc._id;
}
return this.request({
method: 'delete',
uri: '/' + key,
qs: merge({}, options, {
rev: doc._rev
})
}, options);
};
PoachDB.prototype.query = function query(path, options) {
var parts = path.split('/');
var view = parts.pop();
var ddoc = parts.pop();
return this.request({
uri: view ? ('/_design/' + ddoc + '/_view/' + view) : ('/' + path),
qs: options || {}
});
};
PoachDB.prototype.list = function listQuery(path, options) {
var parts = path.split('/');
var view = parts.pop();
var list = parts.pop();
var ddoc = parts.pop();
return this.request({
uri: ['_design', ddoc, '_list', list, view].join('/'),
qs: options || {}
});
};
PoachDB.prototype.info = function info() {
return this.request({
uri: '/'
});
};
PoachDB.prototype.allDocs = function allDocs(options) {
return this.request({
uri: '/_all_docs',
qs: options
});
};
PoachDB.prototype.bulkDocs = function bulkDocs(docs, options) {
return this.request({
method: 'post',
uri: '/_bulk_docs',
qs: options,
body: {
docs: docs
}
});
};
PoachDB.prototype.upsert = function upsert(key, diff) {
debug('upsert(%j)', key);
var db = this;
return this.get(key)
.then(function(doc) {
return doc;
}).catch(function(err) {
// ignoring 404s (it's a new doc)
if (err.status !== 404) {
return Promise.reject(err);
}
return {};
}).then(function(doc) {
var rev = doc._rev;
var newDoc = diff(doc) || doc;
if (newDoc === doc) {
return doc;
}
newDoc._id = key;
newDoc._rev = rev;
return db.put(newDoc).catch(function(err) {
// conflict, try upsert again
if (err.status === 409) {
return upsert(key, diff);
}
return Promise.reject(err);
});
});
};
PoachDB.prototype.request = function request(options) {
debug('request(%j)', options);
// strip double slashes
options.uri = ('/' + this.name + '/' + (options.uri || '')).replace(/\/\/+/g, '/');
return PoachDB.ajax(merge({}, this.options, options));
};
PoachDB.defaults = function factory(prefix, defaults) {
defaults = defaults || {};
if (typeof prefix === 'object') {
defaults = prefix;
}
if (typeof prefix === 'string') {
defaults.baseUrl = prefix;
}
return function clone(name, options) {
return new PoachDB(name, merge({}, defaults, options));
};
};
PoachDB.ajax = function ajax(options, fn) {
return new Promise(function ajaxPromise(resolve, reject) {
debug('ajax(%j)', options);
options.json = options.json || true;
http(options, function(err, res, body) {
if (res && (res.statusCode < 200 || res.statusCode >= 300)) {
console.warn('invalid status code: %s', res.statusCode, body, options);
body = body || {}; // when 500 body is undefined :/
var msg = body.reason || ('invalid status code:' + res.statusCode);
err = new Error(msg);
err.name = body.error;
err.status = res.statusCode;
err.reason = body.reason;
err.message = body.reason;
}
if (err) {
if (fn) {
return fn(err);
} else {
return reject(err);
}
}
if (fn) {
return fn(null, body, res);
} else {
return resolve(body);
}
});
});
};
var chars = (
'0123456789' +
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'abcdefghijklmnopqrstuvwxyz'
).split('');
function getValue(radix) {
return 0 | Math.random() * radix;
}
function uuid(len, radix) {
radix = radix || chars.length;
var out = '';
var i = -1;
if (len) {
// Compact form
while (++i < len) {
out += chars[getValue(radix)];
}
return out;
}
// rfc4122, version 4 form
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
while (++i < 36) {
switch (i) {
case 8:
case 13:
case 18:
case 23:
out += '-';
break;
case 19:
out += chars[(getValue(16) & 0x3) | 0x8];
break;
default:
out += chars[getValue(16)];
}
}
return out;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment