Skip to content

Instantly share code, notes, and snippets.

@benjamind
Created April 11, 2013 19:12
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 benjamind/5366332 to your computer and use it in GitHub Desktop.
Save benjamind/5366332 to your computer and use it in GitHub Desktop.
A simple shim around the couchnode library. If you use this in place of the normal couchbase require then you can use REST protocol to workaround this issue http://www.couchbase.com/issues/browse/JSCBC-22 This shim keeps the interface the same, but allows you to just pass 'true' as an additional argument after the callback argument to force usag…
var couchbase = require('couchbase'),
async = require('async'),
request = require('request'),
url = require('url'),
path = require('path'),
_ = require('underscore'),
apiUrl = null,
_config = null;
var Bucket = function(apiUrl, config) {
this.apiUrl = apiUrl;
this.config = config;
this.requestParams = {
url: apiUrl,
method: 'GET',
headers: {
'Content-type': 'application/json'
},
json: true
};
if (config.user) {
this.requestParams.auth = {
'user': config.user,
'pass': config.password,
'sendImmediately': true
};
}
};
Bucket.prototype = {
query: function(path, params, callback) {
if (typeof params === 'function') {
callback = params;
params = {};
}
var param = _.extend({}, this.requestParams, params);
var uri = _.clone(this.apiUrl);
uri.pathname = '/' + this.config.bucket + path;
param.url = url.format(uri);
request(param, function(err, response, body) {
if (err) {
return callback(err);
}
if (response.statusCode !== 200 && response.statusCode !== 201 && response.statusCode !== 202) {
return callback({headers: response.headers, statusCode: response.statusCode, error: 'received ' + response.statusCode + ' from ' + url.format(param.url)});
}
return callback(null, body);
});
},
getDesignDoc: function(docname, callback) {
this.query('/_design/' + docname, callback);
},
createDesignDoc: function(docname, doc, callback) {
this.query('/_design' + docname, {
method: 'PUT',
json: doc
}, callback);
},
removeDesignDoc: function(docname, callback) {
this.query('/_design' + docname, { method: 'DELETE' }, callback);
},
get: function(id, cb, spooledCb) {
options = null,
that = this;
if (Array.isArray(id)) { // Bulk GET
this.query('/_all_docs', {
qs: { include_docs: true },
json: { keys: id },
method: 'POST'
}, function(err, data) {
if (err) {
if (cb) {
cb(err,null);
}
if (spooledCb) {
spooledCb(err,[],[]);
}
}
if (data.rows.length > 0) {
if (cb) {
data.rows.forEach(function(item) {
if (item.doc) {
cb(err,item.doc.json,item.doc.meta);
} else {
cb(err,null,null);
}
});
}
if (spooledCb) {
var docs = [], metas = [];
data.rows.forEach(function(item) {
if (item.doc) {
docs.push(item.doc.json);
metas.push(item.doc.meta);
}
});
spooledCb(err,docs,metas);
}
} else {
if (cb) {
cb(err,null,null);
}
if (spooledCb) {
spooledCb(err,[],[]);
}
}
});
} else {
this.query('/' + id,{
method: 'GET'
}, cb);
}
},
set: function(id, doc, callback) {
var document = {};
// Bulk Insert
if (Array.isArray(doc)) {
document.docs = doc;
this.query('/_bulk_docs', {
method: 'PUT',
json: doc
}, callback);
} else {
document = doc;
this.query('/' + id, {
json: document,
method: 'PUT'
}, callback);
}
},
remove: function(id, callback) {
this.query('/' + id, { method: 'DELETE' }, callback);
},
incr: function(id, options, callback) {
throw 'Not implemented';
},
view: function(docname, viewname, params, callback) {
throw 'Not implemented';
},
endure: function() {
throw 'Not implemented';
},
observe: function() {
throw 'Not implemented';
}
};
var restConnect = function(config, callback) {
var connected = false, hostsIndex = 0;
async.doWhilst(
function(callback) {
var hostUrl = config.hosts[hostsIndex++],
parsedUrl = url.parse('http://' + hostUrl),
nodesUrl = null,
requestParams = {};
// set path to nodes url
parsedUrl.pathname = '/pools/nodes';
nodesUrl = url.format(parsedUrl);
if (config.user) {
requestParams.auth = {
'user': config.user,
'pass': config.password,
'sendImmediately': true
};
}
requestParams.url = nodesUrl;
// make request
request(requestParams, function(err, response, body) {
if (err) {
return callback(err);
}
if (response.statusCode === 200) {
// get nodes info
var info = JSON.parse(body),
healthyNode = null;
info.nodes.forEach(function(node) {
if (!node.status || node.status!='healthy') {
console.log('found node with status ' + node.status + ', skipping');
return;
}
healthyNode = node;
return;
});
if (!healthyNode) {
console.log('No healthy nodes found at host ' + hostUrl);
callback(); // skip to next host if we have one
}
// store node url
connected = true;
apiUrl = url.parse(healthyNode.couchApiBase);
callback();
} else {
console.log('received ' + response.statusCode + ' from ' + nodesUrl);
callback();
}
});
},
function() {
return (hostsIndex < config.hosts.length) && !connected;
},
function(err) {
if (err) {
return callback(err);
} else {
return callback(null, new Bucket(apiUrl, config));
}
}
);
};
exports.connect = function(config, callback) {
_config = config;
// do my own connect so i know where to send later bucket requests
restConnect(config, function(err, shimBucket) {
couchbase.connect(config, function(err, bkt) {
// store shimbucket for later usage
bkt.shimBucket = shimBucket;
// now extend the existing get/set function to handle workarond param or pass through
var _get = bkt.get;
bkt.get = function(id, cb, spooledCb, workaround) {
if (typeof cb === 'boolean') {
workaround = cb;
cb = undefined;
}
if (typeof spooledCb === 'boolean') {
workaround = spooledCb;
spooledCb = undefined;
}
if (workaround) {
// do REST request instead
this.shimBucket.get(id, cb, spooledCb);
} else {
_get.call(this, id, cb, spooledCb);
}
};
var _set = bkt.set;
bkt.set = function(id, doc, cb, workaround) {
if (workaround) {
// do REST request instead
this.shimBucket.set(id, doc, cb);
} else {
_set.call(this, id, doc, cb);
}
};
var _remove = bkt.remove;
bkt.remove = function(id, cb, workaround) {
if (workaround) {
this.shimBucket.remove(id, cb);
} else {
_remove.call(this, id, cb);
}
};
callback(err, bkt);
});
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment