@corbinu/couchbase
Version:
The unofficial Couchbase Node.js Client Library.
341 lines (286 loc) • 8.64 kB
JavaScript
'use strict';
const async_hooks = require('async_hooks');
const binding = require('./binding');
const connstr = require('./connstr');
const PromiseHelper = require('./promisehelper');
const debug = require('debug')('couchnode:lcb');
const severityLoggers = {
[binding.LCB_LOG_TRACE]: debug.extend('trace'),
[binding.LCB_LOG_DEBUG]: debug.extend('debug'),
[binding.LCB_LOG_INFO]: debug.extend('info'),
[binding.LCB_LOG_WARN]: debug.extend('warn'),
[binding.LCB_LOG_ERROR]: debug.extend('error'),
[binding.LCB_LOG_FATAL]: debug.extend('fatal'),
};
function _logSevToLogger(severity) {
// We cache our loggers above since some versions of the debug library
// incur an disproportional cost (or leak memory) for calling extend.
const logger = severityLoggers[severity];
if (logger) {
return logger;
}
// We still call extend if there is an unexpected severity, this shouldn't
// really happen though...
return debug.extend('sev' + severity);
}
function _logToDebug(info) {
const logger = _logSevToLogger(info.severity);
const location = info.srcFile + ':' + info.srcLine;
logger('(' + info.subsys + ' @ ' + location + ') ' + info.message);
}
/**
* @typedef LoggingEntry
* @type {Object}
* @property {number} severity
* @property {string} srcFile
* @property {number} srcLine
* @property {string} subsys
* @property {string} message
*/
/**
* @typedef LoggingCallback
* @type {function}
* @param {LoggingEntry} entry
*/
/**
* @private
*/
class Connection {
constructor(opts) {
var logFunc = _logToDebug;
if (opts.logFunc) {
logFunc = opts.logFunc;
}
var dsnObj = connstr.parse(opts.connStr);
var lcbDsnObj = connstr.normalize(dsnObj);
if (opts.trustStorePath) {
lcbDsnObj.options.truststorepath = opts.trustStorePath;
}
if (opts.certificatePath) {
lcbDsnObj.options.certpath = opts.certificatePath;
}
if (opts.keyPath) {
lcbDsnObj.options.keypath = opts.keyPath;
}
if (opts.bucketName) {
lcbDsnObj.bucket = opts.bucketName;
}
if (opts.kvConnectTimeout) {
lcbDsnObj.options.config_total_timeout = (
opts.kvConnectTimeout / 1000
).toString();
}
if (opts.kvTimeout) {
lcbDsnObj.options.timeout = (opts.kvTimeout / 1000).toString();
}
if (opts.kvDurableTimeout) {
lcbDsnObj.options.durability_timeout = (
opts.kvDurableTimeout / 1000
).toString();
}
if (opts.viewTimeout) {
lcbDsnObj.options.views_timeout = (opts.viewTimeout / 1000).toString();
}
if (opts.queryTimeout) {
lcbDsnObj.options.query_timeout = (opts.queryTimeout / 1000).toString();
}
if (opts.analyticsTimeout) {
lcbDsnObj.options.analytics_timeout = (
opts.analyticsTimeout / 1000
).toString();
}
if (opts.searchTimeout) {
lcbDsnObj.options.search_timeout = (opts.searchTimeout / 1000).toString();
}
if (opts.managementTimeout) {
lcbDsnObj.options.http_timeout = (
opts.managementTimeout / 1000
).toString();
}
// Grab the various versions. Note that we need to trim them
// off as some Node.js versions insert strange characters into
// the version identifiers (mainly newlines and such).
var couchnodeVer = require('../package.json').version.trim();
var nodeVer = process.versions.node.trim();
var v8Ver = process.versions.v8.trim();
var sslVer = process.versions.openssl.trim();
var clientString =
`couchnode/${couchnodeVer}` +
` (node/${nodeVer}; v8/${v8Ver}; ssl/${sslVer})`;
lcbDsnObj.options.client_string = clientString;
var connType = binding.LCB_TYPE_CLUSTER;
if (lcbDsnObj.bucket) {
connType = binding.LCB_TYPE_BUCKET;
}
var connStr = connstr.stringify(lcbDsnObj);
this._inst = new binding.Connection(
connType,
connStr,
opts.username,
opts.password,
logFunc
);
this._closed = false;
this._pendOps = [];
this._pendBOps = [];
this._connected = false;
this._opened = opts.bucketName ? true : false;
}
async connect() {
return new Promise((resolve, reject) => {
this._inst.connect((err) => {
if (err) {
reject(err);
return;
}
this._connected = true;
this._flushPendOps();
if (this._opened) {
this._flushPendBOps();
}
resolve();
});
});
}
async selectBucket(bucketName, callback) {
const _fwdSelectBucket = function () {
this._maybeFwd(() => this._inst.selectBucket(...arguments), arguments);
}.bind(this);
return PromiseHelper.wrapAsync(async () => {
return new Promise((resolve, reject) => {
_fwdSelectBucket(bucketName, (err) => {
if (err) {
reject(err);
return;
}
this._opened = true;
this._flushPendBOps();
resolve();
});
});
}, callback);
}
_flushPendOps(err = null) {
for (var i = 0; i < this._pendOps.length; ++i) {
this._pendOps[i](err);
}
this._pendOps = [];
}
_flushPendBOps(err = null) {
for (var i = 0; i < this._pendBOps.length; ++i) {
this._pendBOps[i](err);
}
this._pendBOps = [];
}
_maybeFwd(fn, args) {
if (this._closed) {
throw new Error('parent cluster object has been closed');
}
if (args.length == 0) {
throw new Error('attempted to forward function having no arguments');
}
var callback = args[args.length - 1];
if (!(callback instanceof Function)) {
throw new Error('attempted to forward non-callback-based function');
}
if (this._connected) {
fn.call(this._inst, ...args);
} else {
var res = new async_hooks.AsyncResource('cbconnect');
this._pendOps.push((err) => {
if (err) return callback(err);
res.runInAsyncScope(fn, this._inst, ...args);
});
}
}
_maybeBFwd(fn, args) {
if (this._closed) {
throw new Error('parent cluster object has been closed');
}
if (args.length == 0) {
throw new Error('attempted to forward function having no arguments');
}
var callback = args[args.length - 1];
if (!(callback instanceof Function)) {
throw new Error('attempted to forward non-callback-based function');
}
if (this._connected && this._opened) {
try {
fn.call(this._inst, ...args);
} catch (e) {
callback(e);
}
return;
}
var res = new async_hooks.AsyncResource('cbopen');
this._pendBOps.push((err) => {
if (err) return callback(err);
try {
res.runInAsyncScope(fn, this._inst, ...args);
} catch (e) {
callback(e);
}
});
}
close() {
if (this._closed) {
return;
}
this._closed = true;
this._flushPendOps(new Error('cluster object was closed'));
this._flushPendBOps(new Error('cluster object was closed'));
return this._inst.shutdown(...arguments);
}
get() {
this._maybeBFwd(() => this._inst.get(...arguments), arguments);
}
exists() {
this._maybeBFwd(() => this._inst.exists(...arguments), arguments);
}
getReplica() {
this._maybeBFwd(() => this._inst.getReplica(...arguments), arguments);
}
store() {
this._maybeBFwd(() => this._inst.store(...arguments), arguments);
}
remove() {
this._maybeBFwd(() => this._inst.remove(...arguments), arguments);
}
touch() {
this._maybeBFwd(() => this._inst.touch(...arguments), arguments);
}
unlock() {
this._maybeBFwd(() => this._inst.unlock(...arguments), arguments);
}
counter() {
this._maybeBFwd(() => this._inst.counter(...arguments), arguments);
}
lookupIn() {
this._maybeBFwd(() => this._inst.lookupIn(...arguments), arguments);
}
mutateIn() {
this._maybeBFwd(() => this._inst.mutateIn(...arguments), arguments);
}
viewQuery() {
this._maybeFwd(() => this._inst.viewQuery(...arguments), arguments);
}
query() {
this._maybeFwd(() => this._inst.query(...arguments), arguments);
}
analyticsQuery() {
this._maybeFwd(() => this._inst.analyticsQuery(...arguments), arguments);
}
searchQuery() {
this._maybeFwd(() => this._inst.searchQuery(...arguments), arguments);
}
httpRequest() {
this._maybeFwd(() => this._inst.httpRequest(...arguments), arguments);
}
ping() {
this._maybeFwd(() => this._inst.ping(...arguments), arguments);
}
diag() {
this._maybeFwd(() => this._inst.diag(...arguments), arguments);
}
}
module.exports = Connection;