UNPKG

@corbinu/couchbase

Version:

The unofficial Couchbase Node.js Client Library.

341 lines (286 loc) 8.64 kB
'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;