Last active
December 21, 2015 15:19
-
-
Save mnunberg/6326251 to your computer and use it in GitHub Desktop.
(v2)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var couchnode = require('bindings')('couchbase_impl'); | |
var util = require('util'); | |
var CBpp = couchnode.CouchbaseImpl; | |
var CONST = couchnode.Constants; | |
var HOSTKEY_COMPAT = ['hosts', 'hostname', 'hostnames']; | |
/** | |
* Constructor. | |
* @param options a dictionary of options to use. Recognized options | |
* are: | |
* 'host': a string or an array of strings indicating the hosts to | |
* connect to. If the value is an array, all the hosts in the array | |
* will be tried until one of them succeeds. Default is 'localhost' | |
* | |
* 'bucket': the bucket to connect to. If not specified, the default is | |
* 'default'. | |
* | |
* 'username', 'password': The credentials for the bucket. | |
* | |
* | |
* Additionally, other options may be passed in this object which correspond | |
* to the various settings (see their documentation). For example, it may | |
* be helpful to set timeout properties *before* connection. | |
* | |
* @param callback a callback that will be invoked when the instance is actually | |
* connected to the server. | |
* | |
* Note that it is safe to perform operations on the Couchbase object before | |
* the connect callback is invoked. In this case, the operations are queued | |
* until the connection is ready (or an unrecoverable error has taken place). | |
*/ | |
function Couchbase(options, callback) { | |
if (!callback) { | |
callback = function(err) { | |
if (err) console.error(err.stack); | |
}; | |
} | |
if (typeof options !== 'object') { | |
callback(new Error('Options must be an object')); | |
} | |
var ourObjs = {}; | |
for (var kName in options) { | |
if (options.hasOwnProperty(kName)) { | |
ourObjs[kName] = options[kName]; | |
} | |
} | |
var hostKey = 'host'; | |
var hostParam; | |
if (ourObjs.hasOwnProperty(hostKey)) { | |
hostParam = ourObjs[hostKey]; | |
} else { | |
for (var i in HOSTKEY_COMPAT) { | |
var kCompat = HOSTKEY_COMPAT[i]; | |
console.log('Checking.. ' + kCompat); | |
if (!ourObjs[kCompat]) { | |
continue; | |
} | |
hostParam = ourObjs[kCompat]; | |
console.warn(util.format('Using \'%s\' instead of \'host\' ' + | |
'is deprecated', kCompat)); | |
break; | |
} | |
} | |
var cbArgs = []; | |
if (hostParam === undefined) { | |
cbArgs[0] = '127.0.0.1:8091'; | |
} else if (Array.isArray(hostParam)) { | |
cbArgs[0] = hostParam.join(';'); | |
} else { | |
cbArgs[0] = hostParam; | |
} | |
delete ourObjs[hostKey]; | |
// Bucket | |
var argMap = { | |
1: ['username', ''], | |
2: ['password', ''], | |
3: ['bucket', 'default'] | |
}; | |
for (var ix in argMap) { | |
var spec = argMap[ix]; | |
var specName = spec[0]; | |
var specDefault = spec[1]; | |
var curValue = ourObjs[specName]; | |
if (!curValue) { | |
curValue = specDefault; | |
} | |
cbArgs[ix] = curValue; | |
delete ourObjs[specName]; | |
} | |
try { | |
this._cb = new CBpp(cbArgs[0], cbArgs[1], cbArgs[2], cbArgs[3]); | |
} catch (e) { | |
callback(e); | |
} | |
for (var prefOption in ourObjs) { | |
// Check that it exists: | |
var prefValue = ourObjs[prefOption]; | |
if (typeof this[prefOption] === 'undefined') { | |
console.warn('Unknown option: ' + prefOption); | |
} else { | |
this[prefOption](prefValue); | |
} | |
} | |
this._cb.on('connect', callback); | |
try { | |
this._cb._connect(); | |
} catch(e) { | |
callback(e); | |
} | |
} | |
// Merge existing parameters into key-specific ones | |
function mergeParams(key, kParams, gParams) { | |
var ret = {}; | |
ret[key] = kParams; | |
for (var opt in gParams) { | |
// ignore the value parameter as it conflicts with our current one | |
if (opt !== 'value') { | |
kParams[opt] = gParams[opt]; | |
} | |
} | |
return ret; | |
} | |
Couchbase.prototype._invokeStorage = function(tgt, argList) { | |
// (key, value, meta, callback) | |
var meta = mergeParams(argList[0], { value: argList[1] }, argList[2]); | |
tgt.call(this._cb, meta, null, argList[3]); | |
}; | |
/** | |
* Helper to magically invoke a *multi method properly depending on | |
* whether to pass the optional "middle" arg | |
*/ | |
Couchbase.prototype._argHelper2 = function(target, argList) { | |
if (argList.length === 2) { | |
target.call(this._cb, [argList[0]], null, argList[1]); | |
} else { | |
target.call(this._cb, [argList[0]], argList[1], argList[2]); | |
} | |
}; | |
/** | |
* like argHelper2, but also merges params if they exist | |
*/ | |
Couchbase.prototype._argHelperMerge2 = function(target, argList) { | |
if (argList.length === 2) { | |
target.call(this._cb, [argList[0]], null, argList[1]); | |
} else { | |
target.call(this._cb, mergeParams(argList[0], {}, argList[1]), argList[2]); | |
} | |
}; | |
/****************************************************************************** | |
* General Function Protocol ************************************************** | |
* **************************************************************************** | |
* Each of these function take as their last two parameters a 'meta' argument | |
* and a 'callback' argument. | |
* | |
* The 'meta' argument affects various options for the operation itself; each | |
* function will have different options for what goes into the meta. It is | |
* passed as an Object to the function (and may be null) | |
* | |
* The callback itself is also invoked with (err, meta); where 'err' evaluates | |
* to false if there is no error, and otherwise contains one of the following: | |
* 1. An array of [string, object] if a fatal error occured during scheduling | |
* the operation | |
* 2. An integer if the error failed due to network issues or a negative | |
* reply from the server. | |
* | |
* The second argument is an Object containing the details of the response. | |
* Most operations will populate this with a 'cas' property which contains | |
* the opaque value representing the current state of the object. Each time | |
* the object is mutated on the server, this value is modified. | |
* If the 'cas' is supplied in the input meta, the server will check to see | |
* that the cas provided is equal to the current CAS on the server. If they | |
* do not match, the server assumes the user (i.e. you) does not want the | |
* operation to succeed as the mutation supplied may be invalid (from an | |
* application persspective), and would request for the operation to be | |
* executed again. | |
* | |
* A common use case for utilizing this 'CAS' value is to simply pass | |
* the meta received from the callback as the input meta to the next API, | |
* so for example: | |
* | |
* cb.get('a key', null, function(err, res) { | |
* if (err) { | |
* // handle errors here .. | |
return; | |
} | |
var value = JSON.parse(res.value); | |
// Mutate the meta. Assumes the value is an array: | |
if (typeof value !== 'array') { | |
console.error('Didn't get the right type...'); | |
return; | |
} | |
value.push('a new entry'); | |
cb.set('key', value, res) | |
}); | |
* | |
*/ | |
; | |
/** | |
* Get a key from the cluster | |
* | |
* @param key {String} key the key to retrieve | |
* @param options {Object=} meta additional options for this operation | |
* @param callback {Function} the callback to be invoked when complete | |
* | |
* Parameters accept for meta are: | |
* expiry: Also update the expiration time for the key. | |
*/ | |
Couchbase.prototype.get = function(key, options, callback) { | |
this._argHelper2(this._cb.getMulti, arguments); | |
}; | |
/** | |
* Update the item's expiration time in the cluster. | |
* | |
* @param key {String} the key to retrieve | |
* @param options {Object|Undefined=} additional options for this operation. This includes: | |
* 'expiry' - the expiration time to use. If no value is provided, then | |
* the current expiration time is cleared and the key is set to never | |
* expire. Otherwise, the key is updated to expire in the value provided, | |
* in seconds | |
*/ | |
Couchbase.prototype.touch = function(key, options, callback) { | |
this._argHelper2(this._cb.touchMulti, arguments); | |
}; | |
/** | |
* Lock the object on the server and retrieve it. When an object is locked, | |
* its CAS changes and subsequent operations on the object (without providing | |
* the current CAS) will fail until the lock is no longer held | |
* | |
* @param key {String} key the item to lock | |
* @param options {Object|Undefined=} meta options. Options are: | |
* 'expiry' - the duration of time the lock should be held for. If this value | |
* is not provided, it will use the default server-side lock duration which | |
* is 15 seconds. Note that the maximum duration for a lock is 30 seconds, | |
* and if a higher value is specified, it will be rounded to this number | |
* | |
* @param callback {Function} | |
* | |
* Once locked, an item can be unlocked either by explicitly calling unlock(), | |
* or by performing a storage operation (e.g. set, replace, append) with | |
* the current CAS value. | |
*/ | |
Couchbase.prototype.lock = function(key, options, callback) { | |
this._argHelper2(this._cb.lockMulti, arguments); | |
}; | |
/** | |
* Delete a key on the server | |
* @param key {String} the key to remove | |
* @param meta {Undefined|Object=} the options to use. Options are: | |
* 'cas': the CAS value to check. If the item on the server contains a | |
* different CAS value, the operation will fail. | |
* @param callback {Function} | |
*/ | |
Couchbase.prototype.delete = function(key, meta, callback) { | |
this._argHelperMerge2(this._cb.deleteMulti, arguments); | |
}; | |
/** | |
* Unlock a previously locked item on the server | |
* @param key the key to unlock | |
* @param meta options. Note that this takes one option which MUST be | |
* provided, and this is the 'cas' to use for the unlock operation | |
*/ | |
Couchbase.prototype.unlock = function(key, meta, callback) { | |
this._argHelperMerge2(this._cb.unlockMulti, arguments); | |
}; | |
/** | |
* Store a key on the server, setting its value | |
* @param key the key to store | |
* @param value the value the key shall contain | |
* @param meta extra options. Options are: | |
* - cas: (see above for description) | |
* - expiry: set initial expiration for the item | |
* - flags: 32 bit value to use for the item. Note that this value is | |
* not currently used by couchnode, but is used by other clients; and | |
* may be used by Couchnode in the future. The only use case for setting | |
* this value should be intra-client compatibility. Conventionally this | |
* value is used for indicating the storage format of the value. | |
* | |
* Note that if the meta already contains a 'value' field (e.g. from a callback | |
* invoked after a get or getEx operation), it is ignored. | |
*/ | |
Couchbase.prototype.set = function(key, value, meta, callback) { | |
this._invokeStorage(this._cb.setMulti, arguments); | |
}; | |
/** | |
* Like 'set', but will fail if the key already exists | |
*/ | |
Couchbase.prototype.add = function(key, value, meta, callback) { | |
this._invokeStorage(this._cb.addMulti, arguments); | |
}; | |
/** | |
* Like 'set', but will only succeed if the key exists (i.e. | |
* the inverse of add()) | |
*/ | |
Couchbase.prototype.replace = function(key, meta, callback) { | |
this._invokeStorage(this._cb.replaceMulti, arguments); | |
}; | |
/** | |
* Like 'set', but instead of setting a new value, it appends data | |
* to the existing value. Note that this function only makes sense when | |
* the stored item is a string; 'appending' JSON may result in parse | |
* errors when the value is later retrieved | |
*/ | |
Couchbase.prototype.append = function(key, meta, callback) { | |
this._invokeStorage(this._cb.appendMulti, arguments); | |
}; | |
/** | |
* Like 'append', but adds data to the beginning of the value | |
*/ | |
Couchbase.prototype.prepend = function(key, meta, callback) { | |
this._invokeStorage(this._cb.prependMulti, arguments); | |
}; | |
/** | |
* Increments the key's numeric value. This is an atomic operation | |
* and more efficient than set() if the value is a number. | |
* | |
* Note that JavaScript does not support 64 bit integers (while libcouchbase | |
* and the server does). You may end up receiving an invalid value if the | |
* existing number is greater than 64 bits | |
* | |
* @param key the key to increment | |
* @param meta options. Options are: | |
* 'delta': The amount by which to increment; if not specified, the default is | |
* 1 | |
* 'initial': The initial value to use if the key does not exist. If this is | |
* not supplied and the key does not exist, | |
* | |
* 'expiry': the expiration time for the key (may be null, in which case it is | |
* not used). | |
* | |
* Note that as this operation is atomic, no 'cas' parameter is provided. | |
* | |
* If the key already exists but its value is not numeric, an error will be | |
* provided to the callback | |
*/ | |
Couchbase.prototype.incr = function(key, meta, callback) { | |
this._cb.arithmeticMulti(mergeParams(key, { delta: 1 }, meta), null, callback); | |
}; | |
/** | |
* Decrements the key's numeric value. Follows same semantics as 'incr', | |
* with the exception that the 'delta' parameter is the amount by which to | |
* decrement the existing value | |
*/ | |
Couchbase.prototype.decr = function(key, meta, callback) { | |
var ourParams = mergeParams(key, {}, meta); | |
if (ourParams[key].meta) { | |
ourParams[key].meta *= -1; | |
} else { | |
ourParams[key].meta = -1; | |
} | |
this._cb.arithmeticMulti(ourParams, null, callback); | |
}; | |
/** Multi Methods */ | |
Couchbase.prototype.setMulti = function(kv, meta, callback) { | |
this._cb.setMulti.apply(this._cb, arguments); | |
}; | |
Couchbase.prototype.addMulti = function(kv, meta, callback) { | |
this._cb.addMulti.apply(this._cb, arguments); | |
}; | |
Couchbase.prototype.replaceMulti = function(kv, meta, callback) { | |
this._cb.replaceMulti.apply(this._cb, arguments); | |
}; | |
Couchbase.prototype.appendMulti = function(kv, meta, callback) { | |
this._cb.appendMulti.apply(this._cb, arguments); | |
}; | |
Couchbase.prototype.prependMulti = function(kv, meta, callback) { | |
this._cb.prependMulti.apply(this._cb, arguments); | |
}; | |
Couchbase.prototype.getMulti = function(kv, meta, callback) { | |
this._cb.getMulti.apply(this._cb, arguments); | |
}; | |
Couchbase.prototype.lockMulti = function(kv, meta, callback) { | |
this._cb.lockMulti.apply(this._cb, arguments); | |
}; | |
Couchbase.prototype.unlockMulti = function(kv, meta, callback) { | |
this._cb.unlockMulti.apply(this._cb, arguments); | |
}; | |
/** Event Emitters **/ | |
Couchbase.prototype.on = function() { | |
this._cb.on.apply(this._cb, arguments); | |
}; | |
/** Handy and Informational Functions */ | |
Couchbase.prototype.shutdown = function() { | |
this._cb.shutdown(); | |
}; | |
Couchbase.prototype._ctl = function(cc, argList) { | |
if (argList.length === 1) { | |
return this._cb._control(cc, CONST.LCB_CNTL_SET, argList[0]); | |
} else if (argList.length === 0) { | |
return this._cb._control(cc, CONST.LCB_CNTL_GET); | |
} else { | |
throw new Error('Function takes 0 or 1 arguments'); | |
} | |
}; | |
/** | |
* Sets or gets the operation timeout. The operation timeout is the time | |
* that Couchbase will wait for a response from the server. If the response | |
* is not received within this time frame, the operation is bailed out with | |
* an error. | |
* | |
* If called with no arguments, returns the current value. If called with | |
* a single argument, the value is updated | |
* @param msecs the timeout in milliseconds | |
*/ | |
Couchbase.prototype.operationTimeout = function(msecs) { | |
return this._ctl(CONST.LCB_CNTL_OP_TIMEOUT, arguments); | |
}; | |
/** | |
* Sets or gets the connection timeout. This is the timeout value used when | |
* connecting to the configuration port during the initial connection (in this | |
* case, use this as a key in the 'options' parameter in the constructor) and/or | |
* when Couchbase attempts to reconnect in-situ (if the current connection | |
* has failed) | |
* | |
* @param msecs the timeout in milliseconds | |
*/ | |
Couchbase.prototype.connectionTimeout = function(msecs) { | |
return this._ctl(CONST.LCB_CNTL_OP_TIMEOUT, arguments); | |
}; | |
/** | |
* Get information about the libcouchbase version being used. | |
* @return an array of [versionNumber, versionString], where | |
* @c versionNumber is a hexadecimal number of 0x021002 - in this | |
* case indicating version 2.1.2. | |
* | |
* Depending on the build type, this might include some other information | |
* not listed here. | |
*/ | |
Couchbase.prototype.lcbVersion = function() { | |
return this._ctl(CONST.CNTL_LIBCOUCHBASE_VERSION, arguments); | |
}; | |
/** | |
* Get information about the Couchnode version (i.e. this library) | |
* @return an array of [versionNumber, versionString] | |
*/ | |
Couchbase.prototype.clientVersion = function() { | |
return this._ctl(CONST.CNTL_COUCHNODE_VERSION, arguments); | |
}; | |
/** | |
* Get an array of active nodes in the cluster | |
*/ | |
Couchbase.prototype.serverNodes = function() { | |
return this._ctl(CONST.CNTL_CLNODES, arguments); | |
}; | |
module.exports = Couchbase; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment