You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
Add support for camelCase
Fixes missing `EXEC_BATCH` on multi
This commit is contained in:
@@ -49,6 +49,8 @@ This will display:
|
||||
mjr:~/work/node_redis (master)$
|
||||
|
||||
Note that the API is entirely asynchronous. To get data back from the server, you'll need to use a callback.
|
||||
From v.2.6 on the API supports camelCase and snack_case and all options / variables / events etc. can be used either way.
|
||||
It is recommended to use camelCase as this is the default for the Node.js landscape.
|
||||
|
||||
### Promises
|
||||
|
||||
@@ -109,8 +111,6 @@ client.get("missingkey", function(err, reply) {
|
||||
|
||||
For a list of Redis commands, see [Redis Command Reference](http://redis.io/commands)
|
||||
|
||||
The commands can be specified in uppercase or lowercase for convenience. `client.get()` is the same as `client.GET()`.
|
||||
|
||||
Minimal parsing is done on the replies. Commands that return a integer return JavaScript Numbers, arrays return JavaScript Array. `HGETALL` returns an Object keyed by the hash keys. All strings will either be returned as string or as buffer depending on your setting.
|
||||
Please be aware that sending null, undefined and Boolean values will result in the value coerced to a string!
|
||||
|
||||
|
98
index.js
98
index.js
@@ -479,13 +479,19 @@ RedisClient.prototype.send_offline_queue = function () {
|
||||
var retry_connection = function (self, error) {
|
||||
debug('Retrying connection...');
|
||||
|
||||
self.emit('reconnecting', {
|
||||
var reconnect_params = {
|
||||
delay: self.retry_delay,
|
||||
attempt: self.attempts,
|
||||
error: error,
|
||||
times_connected: self.times_connected,
|
||||
total_retry_time: self.retry_totaltime
|
||||
});
|
||||
error: error
|
||||
};
|
||||
if (self.options.camel_case) {
|
||||
reconnect_params.totalRetryTime = self.retry_totaltime;
|
||||
reconnect_params.timesConnected = self.times_connected;
|
||||
} else {
|
||||
reconnect_params.total_retry_time = self.retry_totaltime;
|
||||
reconnect_params.times_connected = self.times_connected;
|
||||
}
|
||||
self.emit('reconnecting', reconnect_params);
|
||||
|
||||
self.retry_totaltime += self.retry_delay;
|
||||
self.attempts += 1;
|
||||
@@ -529,12 +535,18 @@ RedisClient.prototype.connection_gone = function (why, error) {
|
||||
}
|
||||
|
||||
if (typeof this.options.retry_strategy === 'function') {
|
||||
this.retry_delay = this.options.retry_strategy({
|
||||
var retry_params = {
|
||||
attempt: this.attempts,
|
||||
error: error,
|
||||
total_retry_time: this.retry_totaltime,
|
||||
times_connected: this.times_connected
|
||||
});
|
||||
error: error
|
||||
};
|
||||
if (this.options.camel_case) {
|
||||
retry_params.totalRetryTime = this.retry_totaltime;
|
||||
retry_params.timesConnected = this.times_connected;
|
||||
} else {
|
||||
retry_params.total_retry_time = this.retry_totaltime;
|
||||
retry_params.times_connected = this.times_connected;
|
||||
}
|
||||
this.retry_delay = this.options.retry_strategy(retry_params);
|
||||
if (typeof this.retry_delay !== 'number') {
|
||||
// Pass individual error through
|
||||
if (this.retry_delay instanceof Error) {
|
||||
@@ -902,6 +914,72 @@ RedisClient.prototype.write = function (data) {
|
||||
return;
|
||||
};
|
||||
|
||||
Object.defineProperty(exports, 'debugMode', {
|
||||
get: function () {
|
||||
return this.debug_mode;
|
||||
},
|
||||
set: function (val) {
|
||||
this.debug_mode = val;
|
||||
}
|
||||
});
|
||||
|
||||
// Don't officially expose the command_queue directly but only the length as read only variable
|
||||
Object.defineProperty(RedisClient.prototype, 'command_queue_length', {
|
||||
get: function () {
|
||||
return this.command_queue.length;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(RedisClient.prototype, 'offline_queue_length', {
|
||||
get: function () {
|
||||
return this.offline_queue.length;
|
||||
}
|
||||
});
|
||||
|
||||
// Add support for camelCase by adding read only properties to the client
|
||||
// All known exposed snack_case variables are added here
|
||||
Object.defineProperty(RedisClient.prototype, 'retryDelay', {
|
||||
get: function () {
|
||||
return this.retry_delay;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(RedisClient.prototype, 'retryBackoff', {
|
||||
get: function () {
|
||||
return this.retry_backoff;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(RedisClient.prototype, 'commandQueueLength', {
|
||||
get: function () {
|
||||
return this.command_queue.length;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(RedisClient.prototype, 'offlineQueueLength', {
|
||||
get: function () {
|
||||
return this.offline_queue.length;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(RedisClient.prototype, 'shouldBuffer', {
|
||||
get: function () {
|
||||
return this.should_buffer;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(RedisClient.prototype, 'connectionId', {
|
||||
get: function () {
|
||||
return this.connection_id;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(RedisClient.prototype, 'serverInfo', {
|
||||
get: function () {
|
||||
return this.server_info;
|
||||
}
|
||||
});
|
||||
|
||||
exports.createClient = function () {
|
||||
return new RedisClient(unifyOptions.apply(null, arguments));
|
||||
};
|
||||
|
@@ -10,7 +10,7 @@ All documented and exposed API belongs in here
|
||||
**********************************************/
|
||||
|
||||
// Redirect calls to the appropriate function and use to send arbitrary / not supported commands
|
||||
RedisClient.prototype.send_command = function (command, args, callback) {
|
||||
RedisClient.prototype.send_command = RedisClient.prototype.sendCommand = function (command, args, callback) {
|
||||
// Throw to fail early instead of relying in order in this case
|
||||
if (typeof command !== 'string') {
|
||||
throw new Error('Wrong input type "' + (command !== null && command !== undefined ? command.constructor.name : command) + '" for command name');
|
||||
|
@@ -70,7 +70,7 @@ function pipeline_transaction_command (self, command, args, index, cb) {
|
||||
});
|
||||
}
|
||||
|
||||
Multi.prototype.exec_atomic = function exec_atomic (callback) {
|
||||
Multi.prototype.exec_atomic = Multi.prototype.EXEC_ATOMIC = Multi.prototype.execAtomic = function exec_atomic (callback) {
|
||||
if (this.queue.length < 2) {
|
||||
return this.exec_batch(callback);
|
||||
}
|
||||
|
18
lib/utils.js
18
lib/utils.js
@@ -41,8 +41,10 @@ function print (err, reply) {
|
||||
}
|
||||
}
|
||||
|
||||
var camelCase;
|
||||
// Deep clone arbitrary objects with arrays. Can't handle cyclic structures (results in a range error)
|
||||
// Any attribute with a non primitive value besides object and array will be passed by reference (e.g. Buffers, Maps, Functions)
|
||||
// All capital letters are going to be replaced with a lower case letter and a underscore infront of it
|
||||
function clone (obj) {
|
||||
var copy;
|
||||
if (Array.isArray(obj)) {
|
||||
@@ -57,7 +59,14 @@ function clone (obj) {
|
||||
var elems = Object.keys(obj);
|
||||
var elem;
|
||||
while (elem = elems.pop()) {
|
||||
copy[elem] = clone(obj[elem]);
|
||||
// Accept camelCase options and convert them to snack_case
|
||||
var snack_case = elem.replace(/[A-Z][^A-Z]/g, '_$&').toLowerCase();
|
||||
// If camelCase is detected, pass it to the client, so all variables are going to be camelCased
|
||||
// There are no deep nested options objects yet, but let's handle this future proof
|
||||
if (snack_case !== elem.toLowerCase()) {
|
||||
camelCase = true;
|
||||
}
|
||||
copy[snack_case] = clone(obj[elem]);
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
@@ -65,7 +74,12 @@ function clone (obj) {
|
||||
}
|
||||
|
||||
function convenienceClone (obj) {
|
||||
return clone(obj) || {};
|
||||
camelCase = false;
|
||||
obj = clone(obj) || {};
|
||||
if (camelCase) {
|
||||
obj.camel_case = true;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function callbackOrEmit (self, callback, err, res) {
|
||||
|
@@ -166,8 +166,18 @@ describe('client authentication', function () {
|
||||
client = redis.createClient.apply(null, args);
|
||||
client.auth(auth);
|
||||
client.on('ready', function () {
|
||||
if (this.times_connected === 1) {
|
||||
client.stream.destroy();
|
||||
if (this.times_connected < 3) {
|
||||
var interval = setInterval(function () {
|
||||
if (client.commandQueueLength !== 0) {
|
||||
return;
|
||||
}
|
||||
clearInterval(interval);
|
||||
interval = null;
|
||||
client.stream.destroy();
|
||||
client.set('foo', 'bar');
|
||||
client.get('foo'); // Errors would bubble
|
||||
assert.strictEqual(client.offlineQueueLength, 2);
|
||||
}, 1);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
|
@@ -23,16 +23,16 @@ describe("The 'info' method", function () {
|
||||
client.end(true);
|
||||
});
|
||||
|
||||
it('update server_info after a info command', function (done) {
|
||||
it('update serverInfo after a info command', function (done) {
|
||||
client.set('foo', 'bar');
|
||||
client.info();
|
||||
client.select(2, function () {
|
||||
assert.strictEqual(client.server_info.db2, undefined);
|
||||
assert.strictEqual(client.serverInfo.db2, undefined);
|
||||
});
|
||||
client.set('foo', 'bar');
|
||||
client.info();
|
||||
setTimeout(function () {
|
||||
assert.strictEqual(typeof client.server_info.db2, 'object');
|
||||
assert.strictEqual(typeof client.serverInfo.db2, 'object');
|
||||
done();
|
||||
}, 30);
|
||||
});
|
||||
|
@@ -132,12 +132,14 @@ describe('connection tests', function () {
|
||||
|
||||
describe('on lost connection', function () {
|
||||
it('emit an error after max retry attempts and do not try to reconnect afterwards', function (done) {
|
||||
var max_attempts = 3;
|
||||
var maxAttempts = 3;
|
||||
var options = {
|
||||
parser: parser,
|
||||
max_attempts: max_attempts
|
||||
maxAttempts: maxAttempts
|
||||
};
|
||||
client = redis.createClient(options);
|
||||
assert.strictEqual(client.retryBackoff, 1.7);
|
||||
assert.strictEqual(client.retryDelay, 200);
|
||||
assert.strictEqual(Object.keys(options).length, 2);
|
||||
var calls = 0;
|
||||
|
||||
@@ -152,7 +154,7 @@ describe('connection tests', function () {
|
||||
client.on('error', function (err) {
|
||||
if (/Redis connection in broken state: maximum connection attempts.*?exceeded./.test(err.message)) {
|
||||
process.nextTick(function () { // End is called after the error got emitted
|
||||
assert.strictEqual(calls, max_attempts - 1);
|
||||
assert.strictEqual(calls, maxAttempts - 1);
|
||||
assert.strictEqual(client.emitted_end, true);
|
||||
assert.strictEqual(client.connected, false);
|
||||
assert.strictEqual(client.ready, false);
|
||||
@@ -248,7 +250,7 @@ describe('connection tests', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('retry_strategy used to reconnect with individual error', function (done) {
|
||||
it('retryStrategy used to reconnect with individual error', function (done) {
|
||||
var text = '';
|
||||
var unhookIntercept = intercept(function (data) {
|
||||
text += data;
|
||||
@@ -256,8 +258,8 @@ describe('connection tests', function () {
|
||||
});
|
||||
var end = helper.callFuncAfter(done, 2);
|
||||
client = redis.createClient({
|
||||
retry_strategy: function (options) {
|
||||
if (options.total_retry_time > 150) {
|
||||
retryStrategy: function (options) {
|
||||
if (options.totalRetryTime > 150) {
|
||||
client.set('foo', 'bar', function (err, res) {
|
||||
assert.strictEqual(err.message, 'Connection timeout');
|
||||
end();
|
||||
@@ -267,8 +269,8 @@ describe('connection tests', function () {
|
||||
}
|
||||
return Math.min(options.attempt * 25, 200);
|
||||
},
|
||||
max_attempts: 5,
|
||||
retry_max_delay: 123,
|
||||
maxAttempts: 5,
|
||||
retryMaxDelay: 123,
|
||||
port: 9999
|
||||
});
|
||||
|
||||
|
@@ -571,7 +571,7 @@ describe("The 'multi' method", function () {
|
||||
test = true;
|
||||
};
|
||||
multi.set('baz', 'binary');
|
||||
multi.exec_atomic();
|
||||
multi.EXEC_ATOMIC();
|
||||
assert(test);
|
||||
});
|
||||
|
||||
|
@@ -30,6 +30,7 @@ describe('The node_redis client', function () {
|
||||
it('check if all options got copied properly', function (done) {
|
||||
client.selected_db = 2;
|
||||
var client2 = client.duplicate();
|
||||
assert.strictEqual(client.connectionId + 1, client2.connection_id);
|
||||
assert.strictEqual(client2.selected_db, 2);
|
||||
assert(client.connected);
|
||||
assert(!client2.connected);
|
||||
@@ -360,7 +361,7 @@ describe('The node_redis client', function () {
|
||||
client.on('error', function (err) {
|
||||
assert.strictEqual(err.message, 'SET can\'t be processed. The connection has already been closed.');
|
||||
assert.strictEqual(err.command, 'SET');
|
||||
assert.strictEqual(client.offline_queue.length, 0);
|
||||
assert.strictEqual(client.offline_queue_length, 0);
|
||||
done();
|
||||
});
|
||||
setTimeout(function () {
|
||||
@@ -966,7 +967,7 @@ describe('The node_redis client', function () {
|
||||
multi.set('foo' + (i + 2), 'bar' + (i + 2));
|
||||
}
|
||||
multi.exec();
|
||||
assert.equal(client.command_queue.length, 15);
|
||||
assert.equal(client.command_queue_length, 15);
|
||||
helper.killConnection(client);
|
||||
});
|
||||
|
||||
|
@@ -11,7 +11,7 @@ describe('utils.js', function () {
|
||||
it('ignore the object prototype and clone a nested array / object', function () {
|
||||
var obj = {
|
||||
a: [null, 'foo', ['bar'], {
|
||||
"I'm special": true
|
||||
"i'm special": true
|
||||
}],
|
||||
number: 5,
|
||||
fn: function noop () {}
|
||||
@@ -22,13 +22,28 @@ describe('utils.js', function () {
|
||||
assert(typeof clone.fn === 'function');
|
||||
});
|
||||
|
||||
it('replace faulty values with an empty object as return value', function () {
|
||||
it('replace falsy values with an empty object as return value', function () {
|
||||
var a = utils.clone();
|
||||
var b = utils.clone(null);
|
||||
assert.strictEqual(Object.keys(a).length, 0);
|
||||
assert.strictEqual(Object.keys(b).length, 0);
|
||||
});
|
||||
|
||||
it('transform camelCase options to snack_case and add the camel_case option', function () {
|
||||
var a = utils.clone({
|
||||
optionOneTwo: true,
|
||||
retryStrategy: false,
|
||||
nested: {
|
||||
onlyContainCamelCaseOnce: true
|
||||
}
|
||||
});
|
||||
assert.strictEqual(Object.keys(a).length, 4);
|
||||
assert.strictEqual(a.option_one_two, true);
|
||||
assert.strictEqual(a.retry_strategy, false);
|
||||
assert.strictEqual(a.camel_case, true);
|
||||
assert.strictEqual(Object.keys(a.nested).length, 1);
|
||||
});
|
||||
|
||||
it('throws on circular data', function () {
|
||||
try {
|
||||
var a = {};
|
||||
|
Reference in New Issue
Block a user