lib/drivers/database/nano.js
var nano = require('nano')
, Promise = require('bluebird')
, _ = require('lodash')
, fs = Promise.promisifyAll(require('fs'))
, path = require('path')
, util = require('../../util');
module.exports = function (config) {
return new Database(config.db);
};
function Database (config) {
var protocol = (config.https && config.https !== 'false') ? 'https' : 'http'
, port = config.port == '80' || config.port == '443' ? '' : (':' + config.port)
, authString = config.auth ? (config.auth + '@') : '';
this.config = config;
this.connString = protocol + '://' + authString + config.host + port;
console.log('Connecting to database `' + config.name + '` (' + config.driver + '): ' + this.connString);
this.db = Promise.promisifyAll(nano(this.connString + '/' + config.name));
this.nano = Promise.promisifyAll(nano(this.connString).db);
this.attachments = Promise.promisifyAll(this.db.attachment);
}
Database.prototype.get = function (name) {
if (_.isArray(name)) {
return Promise.all(name.map(this.get.bind(this)));
}
return this.db.getAsync(name._id || name);
};
Database.prototype.put = function (name, doc, force) {
var args = []
, self = this;
if (force && force === true) {
return this.get(name)
.then(function (results) {
if (results.length === 0) {
return self.put(name, doc);
}
else {
return self.purge(name)
.then(function () {
return self.put(name, doc);
});
}
})
.catch(function (err) {
if (err.message === 'missing' || err.message === 'deleted') {
return self.put(name, doc);
}
console.log('Error putting document: ', err);
});
}
if (typeof name === 'string') {
args = [doc, name];
}
else if (name._id) {
args = [name, name._id];
}
else {
args = [name];
}
return this.db.insertAsync.apply(this.db, args);
};
Database.prototype.use = function (name) {
var config = _.extend({}, this.config, {name: name});
return new Database(config);
};
Database.prototype.view = function (designDocName, viewName, params) {
if (params) {
return this.db.viewAsync(designDocName, viewName, params);
}
else {
return this.db.viewAsync(designDocName, viewName);
}
};
Database.prototype.fetch = function (ids) {
return this.db.fetchAsync({keys: ids});
};
Database.prototype.update = function (designDocName, updateName, doc) {
// nano.db.atomic is broken, so we have to create our own request
// this will behave according to the nano docs (use req.form to get params)
var opts = {
db: this.config.name,
path: '_design/' + designDocName + '/_update/' + updateName,
method: 'POST',
content_type: 'application/x-www-form-urlencoded',
form: doc
};
if (doc._id) {
opts.method = 'PUT';
opts.path += '/' + doc._id;
}
return this.relax(opts);
};
Database.prototype.relax = function (opts) {
var defaults = {
db: this.config.name
};
var opts = _.extend({}, defaults, opts);
return Promise.promisifyAll(nano(this.connString)).relaxAsync(opts);
};
Database.prototype.attach = function (name, filePaths) {
var self = this
, doc = (typeof name === 'string' ? this.get(name) : Promise.resolve([name]));
if (filePaths.length === 0) {
return doc.get(0);
}
if (typeof filePaths === 'string') {
filePaths = [filePaths];
}
return doc
.then(function (results) {
doc = results[0] || results;
return Promise.all(filePaths.map(util.getFileForAttachment));
})
.then(function (files) {
return new Promise(function (resolve, reject) {
self.db.multipart.insert(doc, files, doc._id, function (err, body) {
if (err) {
reject(err);
}
else {
_.extend(doc._attachments, body._attachments);
resolve(doc);
}
});
});
});
};
Database.prototype.destroyDb = function (name) {
if (_.isArray(name)) {
return Promise.all(name.map(this.destroyDb.bind(this)));
}
return this.nano.destroyAsync(name);
};
Database.prototype.destroy = function (name) {
var self = this;
if (_.isArray(name)) {
return Promise.all(name.map(this.destroy.bind(this)));
}
return this.get(name)
.get(0)
.get('_rev')
.then(function (rev) {
return self.db.destroyAsync(name, rev);
});
};
Database.prototype.purge = function (name) {
var conn = this.connString
, self = this;
var opts = {
method: 'POST',
path: '_purge',
body: {}
};
if (conn.indexOf('.cloudant.com') >= 0) {
// Cloudant doesn't support purge, so we'll just do a regular delete
console.log('Warning, Cloudant doesn\'t support purge.');
console.log('Doing a regular delete for now.');
return this.destroy(name);
}
return this.get(name)
.then(function (result) {
var doc = result[0] || result;
var rev = doc._rev;
opts.body[name] = [rev];
return self.relax(opts);
})
.catch(function (err) {
if (err.message === 'missing') {
// good enough for me
return true;
}
});
};
Database.prototype.createDb = function (name) {
var conn = this.connString;
if (_.isArray(name)) {
return Promise.all(name.map(this.createDb.bind(this)));
}
return new Promise(function (resolve, reject) {
nano(conn).db.create(name, function (err, body) {
if (err && err.error !== 'file_exists') {
// If it already exists, we're good
reject(err);
}
else {
resolve(body);
}
});
});
};
Database.prototype.list = function () {
var conn = this.connString;
return new Promise(function (resolve, reject) {
nano(conn).db.list(function (err, body) {
if (err) {
reject(err);
}
else {
resolve(body);
}
});
});
};
Database.prototype.detach = function (docId, attId) {
var self = this;
return this.get(docId)
.get(0)
.then(function (doc) {
return self.attachments.destroyAsync(docId, attId, doc._rev);
});
};
Database.prototype.getAttachment = function (docId, attId) {
var self = this;
// Don't ask me why using this.attachments doesn't work.
// It just doesn't.
return new Promise(function (resolve, reject) {
self.db.attachment.get(docId, attId, function (err, body) {
if (err) {
reject(err);
}
else {
resolve(body);
}
});
});
};
Database.prototype.feed = function (opts) {
return this.db.follow(opts);
};
Database.prototype.follow = function (opts, handler, nofollow) {
var feed = this.feed(opts);
feed.on('changes', handler);
if (!opts.noFollow && !nofollow) {
feed.follow();
}
return feed;
};
Database.prototype.bulk = function (docs, params) {
var bulkInput = (!docs.docs) ? {docs:docs} : docs;
return this.db.bulkAsync(bulkInput, params);
};