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)$
|
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.
|
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
|
### 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)
|
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.
|
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!
|
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) {
|
var retry_connection = function (self, error) {
|
||||||
debug('Retrying connection...');
|
debug('Retrying connection...');
|
||||||
|
|
||||||
self.emit('reconnecting', {
|
var reconnect_params = {
|
||||||
delay: self.retry_delay,
|
delay: self.retry_delay,
|
||||||
attempt: self.attempts,
|
attempt: self.attempts,
|
||||||
error: error,
|
error: error
|
||||||
times_connected: self.times_connected,
|
};
|
||||||
total_retry_time: self.retry_totaltime
|
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.retry_totaltime += self.retry_delay;
|
||||||
self.attempts += 1;
|
self.attempts += 1;
|
||||||
@@ -529,12 +535,18 @@ RedisClient.prototype.connection_gone = function (why, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof this.options.retry_strategy === 'function') {
|
if (typeof this.options.retry_strategy === 'function') {
|
||||||
this.retry_delay = this.options.retry_strategy({
|
var retry_params = {
|
||||||
attempt: this.attempts,
|
attempt: this.attempts,
|
||||||
error: error,
|
error: error
|
||||||
total_retry_time: this.retry_totaltime,
|
};
|
||||||
times_connected: this.times_connected
|
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') {
|
if (typeof this.retry_delay !== 'number') {
|
||||||
// Pass individual error through
|
// Pass individual error through
|
||||||
if (this.retry_delay instanceof Error) {
|
if (this.retry_delay instanceof Error) {
|
||||||
@@ -902,6 +914,72 @@ RedisClient.prototype.write = function (data) {
|
|||||||
return;
|
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 () {
|
exports.createClient = function () {
|
||||||
return new RedisClient(unifyOptions.apply(null, arguments));
|
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
|
// 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
|
// Throw to fail early instead of relying in order in this case
|
||||||
if (typeof command !== 'string') {
|
if (typeof command !== 'string') {
|
||||||
throw new Error('Wrong input type "' + (command !== null && command !== undefined ? command.constructor.name : command) + '" for command name');
|
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) {
|
if (this.queue.length < 2) {
|
||||||
return this.exec_batch(callback);
|
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)
|
// 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)
|
// 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) {
|
function clone (obj) {
|
||||||
var copy;
|
var copy;
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
@@ -57,7 +59,14 @@ function clone (obj) {
|
|||||||
var elems = Object.keys(obj);
|
var elems = Object.keys(obj);
|
||||||
var elem;
|
var elem;
|
||||||
while (elem = elems.pop()) {
|
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;
|
return copy;
|
||||||
}
|
}
|
||||||
@@ -65,7 +74,12 @@ function clone (obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function convenienceClone (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) {
|
function callbackOrEmit (self, callback, err, res) {
|
||||||
|
@@ -166,8 +166,18 @@ describe('client authentication', function () {
|
|||||||
client = redis.createClient.apply(null, args);
|
client = redis.createClient.apply(null, args);
|
||||||
client.auth(auth);
|
client.auth(auth);
|
||||||
client.on('ready', function () {
|
client.on('ready', function () {
|
||||||
if (this.times_connected === 1) {
|
if (this.times_connected < 3) {
|
||||||
client.stream.destroy();
|
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 {
|
} else {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
|
@@ -23,16 +23,16 @@ describe("The 'info' method", function () {
|
|||||||
client.end(true);
|
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.set('foo', 'bar');
|
||||||
client.info();
|
client.info();
|
||||||
client.select(2, function () {
|
client.select(2, function () {
|
||||||
assert.strictEqual(client.server_info.db2, undefined);
|
assert.strictEqual(client.serverInfo.db2, undefined);
|
||||||
});
|
});
|
||||||
client.set('foo', 'bar');
|
client.set('foo', 'bar');
|
||||||
client.info();
|
client.info();
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
assert.strictEqual(typeof client.server_info.db2, 'object');
|
assert.strictEqual(typeof client.serverInfo.db2, 'object');
|
||||||
done();
|
done();
|
||||||
}, 30);
|
}, 30);
|
||||||
});
|
});
|
||||||
|
@@ -132,12 +132,14 @@ describe('connection tests', function () {
|
|||||||
|
|
||||||
describe('on lost connection', function () {
|
describe('on lost connection', function () {
|
||||||
it('emit an error after max retry attempts and do not try to reconnect afterwards', function (done) {
|
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 = {
|
var options = {
|
||||||
parser: parser,
|
parser: parser,
|
||||||
max_attempts: max_attempts
|
maxAttempts: maxAttempts
|
||||||
};
|
};
|
||||||
client = redis.createClient(options);
|
client = redis.createClient(options);
|
||||||
|
assert.strictEqual(client.retryBackoff, 1.7);
|
||||||
|
assert.strictEqual(client.retryDelay, 200);
|
||||||
assert.strictEqual(Object.keys(options).length, 2);
|
assert.strictEqual(Object.keys(options).length, 2);
|
||||||
var calls = 0;
|
var calls = 0;
|
||||||
|
|
||||||
@@ -152,7 +154,7 @@ describe('connection tests', function () {
|
|||||||
client.on('error', function (err) {
|
client.on('error', function (err) {
|
||||||
if (/Redis connection in broken state: maximum connection attempts.*?exceeded./.test(err.message)) {
|
if (/Redis connection in broken state: maximum connection attempts.*?exceeded./.test(err.message)) {
|
||||||
process.nextTick(function () { // End is called after the error got emitted
|
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.emitted_end, true);
|
||||||
assert.strictEqual(client.connected, false);
|
assert.strictEqual(client.connected, false);
|
||||||
assert.strictEqual(client.ready, 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 text = '';
|
||||||
var unhookIntercept = intercept(function (data) {
|
var unhookIntercept = intercept(function (data) {
|
||||||
text += data;
|
text += data;
|
||||||
@@ -256,8 +258,8 @@ describe('connection tests', function () {
|
|||||||
});
|
});
|
||||||
var end = helper.callFuncAfter(done, 2);
|
var end = helper.callFuncAfter(done, 2);
|
||||||
client = redis.createClient({
|
client = redis.createClient({
|
||||||
retry_strategy: function (options) {
|
retryStrategy: function (options) {
|
||||||
if (options.total_retry_time > 150) {
|
if (options.totalRetryTime > 150) {
|
||||||
client.set('foo', 'bar', function (err, res) {
|
client.set('foo', 'bar', function (err, res) {
|
||||||
assert.strictEqual(err.message, 'Connection timeout');
|
assert.strictEqual(err.message, 'Connection timeout');
|
||||||
end();
|
end();
|
||||||
@@ -267,8 +269,8 @@ describe('connection tests', function () {
|
|||||||
}
|
}
|
||||||
return Math.min(options.attempt * 25, 200);
|
return Math.min(options.attempt * 25, 200);
|
||||||
},
|
},
|
||||||
max_attempts: 5,
|
maxAttempts: 5,
|
||||||
retry_max_delay: 123,
|
retryMaxDelay: 123,
|
||||||
port: 9999
|
port: 9999
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -571,7 +571,7 @@ describe("The 'multi' method", function () {
|
|||||||
test = true;
|
test = true;
|
||||||
};
|
};
|
||||||
multi.set('baz', 'binary');
|
multi.set('baz', 'binary');
|
||||||
multi.exec_atomic();
|
multi.EXEC_ATOMIC();
|
||||||
assert(test);
|
assert(test);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -30,6 +30,7 @@ describe('The node_redis client', function () {
|
|||||||
it('check if all options got copied properly', function (done) {
|
it('check if all options got copied properly', function (done) {
|
||||||
client.selected_db = 2;
|
client.selected_db = 2;
|
||||||
var client2 = client.duplicate();
|
var client2 = client.duplicate();
|
||||||
|
assert.strictEqual(client.connectionId + 1, client2.connection_id);
|
||||||
assert.strictEqual(client2.selected_db, 2);
|
assert.strictEqual(client2.selected_db, 2);
|
||||||
assert(client.connected);
|
assert(client.connected);
|
||||||
assert(!client2.connected);
|
assert(!client2.connected);
|
||||||
@@ -360,7 +361,7 @@ describe('The node_redis client', function () {
|
|||||||
client.on('error', function (err) {
|
client.on('error', function (err) {
|
||||||
assert.strictEqual(err.message, 'SET can\'t be processed. The connection has already been closed.');
|
assert.strictEqual(err.message, 'SET can\'t be processed. The connection has already been closed.');
|
||||||
assert.strictEqual(err.command, 'SET');
|
assert.strictEqual(err.command, 'SET');
|
||||||
assert.strictEqual(client.offline_queue.length, 0);
|
assert.strictEqual(client.offline_queue_length, 0);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
@@ -966,7 +967,7 @@ describe('The node_redis client', function () {
|
|||||||
multi.set('foo' + (i + 2), 'bar' + (i + 2));
|
multi.set('foo' + (i + 2), 'bar' + (i + 2));
|
||||||
}
|
}
|
||||||
multi.exec();
|
multi.exec();
|
||||||
assert.equal(client.command_queue.length, 15);
|
assert.equal(client.command_queue_length, 15);
|
||||||
helper.killConnection(client);
|
helper.killConnection(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ describe('utils.js', function () {
|
|||||||
it('ignore the object prototype and clone a nested array / object', function () {
|
it('ignore the object prototype and clone a nested array / object', function () {
|
||||||
var obj = {
|
var obj = {
|
||||||
a: [null, 'foo', ['bar'], {
|
a: [null, 'foo', ['bar'], {
|
||||||
"I'm special": true
|
"i'm special": true
|
||||||
}],
|
}],
|
||||||
number: 5,
|
number: 5,
|
||||||
fn: function noop () {}
|
fn: function noop () {}
|
||||||
@@ -22,13 +22,28 @@ describe('utils.js', function () {
|
|||||||
assert(typeof clone.fn === '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 a = utils.clone();
|
||||||
var b = utils.clone(null);
|
var b = utils.clone(null);
|
||||||
assert.strictEqual(Object.keys(a).length, 0);
|
assert.strictEqual(Object.keys(a).length, 0);
|
||||||
assert.strictEqual(Object.keys(b).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 () {
|
it('throws on circular data', function () {
|
||||||
try {
|
try {
|
||||||
var a = {};
|
var a = {};
|
||||||
|
Reference in New Issue
Block a user