Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ioredis

Package Overview
Dependencies
Maintainers
1
Versions
228
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ioredis - npm Package Compare versions

Comparing version 1.0.13 to 1.1.0

benchmarks/.jshintrc

58

API.md

@@ -5,7 +5,11 @@ ## Classes

<dd></dd>
<dt><a href="#RedisCluster">RedisCluster</a> ⇐ <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code></dt>
<dd></dd>
<dt><a href="#Commander">Commander</a></dt>
<dd></dd>
</dl>
## Members
<dl>
<dt><a href="#defaultOptions">defaultOptions</a></dt>
<dd><p>Default options</p>
</dd>
</dl>
<a name="Redis"></a>

@@ -77,3 +81,3 @@ ## Redis ⇐ <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code>

This method closes the connection immediately,
and may lose some pending replies that haven't written to clien.
and may lose some pending replies that haven't written to client.
If you want to wait for the pending replies, use Redis#quit instead.

@@ -153,44 +157,2 @@

**Kind**: static method of <code>[Redis](#Redis)</code>
<a name="RedisCluster"></a>
## RedisCluster ⇐ <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code>
**Kind**: global class
**Extends:** <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code>, <code>[Commander](#Commander)</code>
* [RedisCluster](#RedisCluster) ⇐ <code>[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)</code>
* [new RedisCluster(startupNodes, options)](#new_RedisCluster_new)
* [.disconnect()](#RedisCluster#disconnect)
* [.defineCommand(name, definition)](#Commander#defineCommand)
<a name="new_RedisCluster_new"></a>
### new RedisCluster(startupNodes, options)
Creates a Redis instance
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| startupNodes | <code>Array.&lt;Object&gt;</code> | | An array of nodes in the cluster, [{ port: number, host: string }] |
| options | <code>Object</code> | | |
| [options.enableOfflineQueue] | <code>boolean</code> | <code>true</code> | See Redis class |
| [options.lazyConnect] | <code>boolean</code> | <code>true</code> | See Redis class |
| [options.refreshAfterFails] | <code>number</code> | <code>10</code> | When a MOVED error is returned, it's considered a failure. When the times of failures reach `refreshAfterFails`, client will call CLUSTER SLOTS command to refresh the slots. |
<a name="RedisCluster#disconnect"></a>
### redisCluster.disconnect()
Disconnect from every node in the cluster.
**Kind**: instance method of <code>[RedisCluster](#RedisCluster)</code>
**Access:** public
<a name="Commander#defineCommand"></a>
### redisCluster.defineCommand(name, definition)
Define a custom command using lua script
**Kind**: instance method of <code>[RedisCluster](#RedisCluster)</code>
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| name | <code>string</code> | | the command name |
| definition | <code>object</code> | | |
| definition.lua | <code>string</code> | | the lua code |
| [definition.numberOfKeys] | <code>number</code> | <code></code> | the number of keys. If omit, you have to pass the number of keys as the first argument every time you invoke the command |
<a name="Commander"></a>

@@ -226,1 +188,7 @@ ## Commander

<a name="defaultOptions"></a>
## defaultOptions
Default options
**Kind**: global variable
**Access:** protected

@@ -5,2 +5,10 @@ ## Changelog

### v1.1.0 - May 1, 2015
* Support cluster auto reconnection.
* Add `maxRedirections` option to Cluster.
* Remove `roleRetryDelay` option in favor of `sentinelRetryStrategy`.
* Improve compatibility with node_redis.
* More stable sentinel connection.
### v1.0.13 - April 27, 2015

@@ -7,0 +15,0 @@

@@ -1,1 +0,5 @@

module.exports = require('./lib/redis');
exports = module.exports = require('./lib/redis');
exports.ReplyError = require('./lib/reply_error');
exports.Promise = require('bluebird');
exports.Cluster = require('./lib/cluster');

@@ -8,2 +8,3 @@ 'use strict';

var commands = require('ioredis-commands');
/**

@@ -65,58 +66,84 @@ * Command instance

Command.prototype.getSlot = function () {
if (typeof this._slot === 'undefined') {
var key = this.getKeys()[0];
if (key) {
this.slot = utils.calcSlot(key);
} else {
this.slot = null;
}
}
return this.slot;
};
Command.prototype.getKeys = function () {
var keys = [];
var i, keyStart, keyStop;
var def = commands[this.name];
if (def) {
switch (this.name) {
case 'eval':
case 'evalsha':
keyStop = parseInt(this.args[1], 10) + 2;
for (i = 2; i < keyStop; ++i) {
keys.push(this.args[i]);
}
break;
case 'sort':
keys.push(this.args[0]);
for (i = 1; i < this.args.length - 1; ++i) {
if (typeof this.args[i] !== 'string') {
continue;
if (typeof this._keys === 'undefined') {
this._keys = [];
var i, keyStart, keyStop;
var def = commands[this.name];
if (def) {
switch (this.name) {
case 'eval':
case 'evalsha':
keyStop = parseInt(this.args[1], 10) + 2;
for (i = 2; i < keyStop; ++i) {
this._keys.push(this.args[i]);
}
var directive = this.args[i].toUpperCase();
if (directive === 'GET') {
i += 1;
if (this.args[i] !== '#') {
keys.push(this.getKeyPart(this.args[i]));
break;
case 'sort':
this._keys.push(this.args[0]);
for (i = 1; i < this.args.length - 1; ++i) {
if (typeof this.args[i] !== 'string') {
continue;
}
} else if (directive === 'BY') {
i += 1;
keys.push(this.getKeyPart(this.args[i]));
} else if (directive === 'STORE') {
i += 1;
keys.push(this.args[i]);
var directive = this.args[i].toUpperCase();
if (directive === 'GET') {
i += 1;
if (this.args[i] !== '#') {
this._keys.push(this.getKeyPart(this.args[i]));
}
} else if (directive === 'BY') {
i += 1;
this._keys.push(this.getKeyPart(this.args[i]));
} else if (directive === 'STORE') {
i += 1;
this._keys.push(this.args[i]);
}
}
}
break;
case 'zunionstore':
case 'zinterstore':
keys.push(this.args[0]);
keyStop = parseInt(this.args[1], 10) + 2;
for (i = 2; i < keyStop; ++i) {
keys.push(this.args[i]);
}
break;
default:
keyStart = def.keyStart - 1;
keyStop = def.keyStop > 0 ? def.keyStop : this.args.length + def.keyStop + 1;
if (keyStart >= 0 && keyStop <= this.args.length && keyStop > keyStart && def.step > 0) {
for (i = keyStart; i < keyStop; i += def.step) {
keys.push(this.args[i]);
break;
case 'zunionstore':
case 'zinterstore':
this._keys.push(this.args[0]);
keyStop = parseInt(this.args[1], 10) + 2;
for (i = 2; i < keyStop; ++i) {
this._keys.push(this.args[i]);
}
break;
default:
keyStart = def.keyStart - 1;
keyStop = def.keyStop > 0 ? def.keyStop : this.args.length + def.keyStop + 1;
if (keyStart >= 0 && keyStop <= this.args.length && keyStop > keyStart && def.step > 0) {
for (i = keyStart; i < keyStop; i += def.step) {
this._keys.push(this.args[i]);
}
}
break;
}
break;
}
}
return keys;
return this._keys;
};
Command.prototype.getKeyPart = function (key) {
var starPos = key.indexOf('*');
if (starPos === -1) {
return key;
}
var hashPos = key.indexOf('->', starPos + 1);
if (hashPos === 1) {
return key;
}
return key.slice(0, hashPos);
};
/**

@@ -231,2 +258,37 @@ * Convert command to writable buffer or string

Command.setArgumentTransformer('mset', function (args) {
if (args.length === 1) {
if (typeof Map !== 'undefined' && args[0] instanceof Map) {
return utils.convertMapToArray(args[0]);
}
if ( typeof args[0] === 'object' && args[0] !== null) {
return utils.convertObjectToArray(args[0]);
}
}
return args;
});
Command.setArgumentTransformer('hmset', function (args) {
if (args.length === 2) {
if (typeof Map !== 'undefined' && args[1] instanceof Map) {
return [args[0]].concat(utils.convertMapToArray(args[1]));
}
if ( typeof args[1] === 'object' && args[1] !== null) {
return [args[0]].concat(utils.convertObjectToArray(args[1]));
}
}
return args;
});
Command.setReplyTransformer('hgetall', function (result) {
if (Array.isArray(result)) {
var obj = {};
for (var i = 0; i < result.length; i += 2) {
obj[result[i]] = result[i + 1];
}
return obj;
}
return result;
});
module.exports = Command;

@@ -6,3 +6,3 @@ 'use strict';

var hiredis = require('hiredis');
var ReplyError = require('../redis').ReplyError;
var ReplyError = require('../reply_error');

@@ -9,0 +9,0 @@ function HiredisReplyParser(options) {

@@ -5,3 +5,4 @@ 'use strict';

var util = require('util');
var ReplyError = require('../redis').ReplyError;
var utils = require('../utils');
var ReplyError = require('../reply_error');

@@ -26,11 +27,4 @@ function Packet(type, size) {

function IncompleteReadBuffer(message) {
Error.call(this);
Error.captureStackTrace(this, this.constructor);
var IncompleteReadBuffer = utils.extendsError('IncompleteReadBuffer');
this.name = this.constructor.name;
this.message = message;
}
util.inherits(IncompleteReadBuffer, Error);
// Buffer.toString() is quite slow for small strings

@@ -37,0 +31,0 @@ function smallToString(buf, start, end) {

@@ -110,3 +110,3 @@ 'use strict';

if (!--writePending) {
_this.redis.connection.write(data);
_this.redis.stream.write(data);
}

@@ -113,0 +113,0 @@ }

@@ -6,5 +6,3 @@ 'use strict';

var EventEmitter = require('events').EventEmitter;
var net = require('net');
var Promise = require('bluebird');
var url = require('url');
var Pipeline = require('./pipeline');

@@ -16,2 +14,10 @@ var Command = require('./command');

var debug = require('debug')('ioredis:redis');
var Connector = require('./connectors/connector');
var SentinelConnector = require('./connectors/sentinel_connector');
var JavaScriptParser = require('./parsers/javascript');
var NativeParser;
try {
NativeParser = require('./parsers/hiredis');
} catch (e) {
}

@@ -84,5 +90,5 @@ /**

*/
function Redis(port, host, options) {
function Redis() {
if (!(this instanceof Redis)) {
return new Redis(port, host, options);
return new Redis(arguments[0], arguments[1], arguments[2]);
}

@@ -93,48 +99,8 @@

var resultOptions;
if (typeof port === 'object') {
// Redis(options)
resultOptions = port;
} else if (typeof port === 'string' && !utils.isInt(port)) {
// Redis(url, options)
var parsedOptions = {};
var parsedURL = url.parse(port, true, true);
if (parsedURL.hostname) {
parsedOptions.port = parsedURL.port;
parsedOptions.host = parsedURL.hostname;
if (parsedURL.auth) {
parsedOptions.password = parsedURL.auth.split(':')[1];
}
if (parsedURL.path) {
parsedOptions.db = parseInt(parsedURL.path.slice(1), 10);
}
} else {
parsedOptions.path = port;
}
resultOptions = _.defaults(host || {}, parsedOptions);
} else {
// Redis(port, host, options) or Redis(port, options)
if (host && typeof host === 'object') {
resultOptions = _.defaults(host, { port: port });
} else {
resultOptions = _.defaults(options || {}, { port: port, host: host });
}
}
if (resultOptions && typeof resultOptions.port === 'string') {
resultOptions.port = parseInt(resultOptions.port, 10);
}
this.parseOptions(arguments[0], arguments[1], arguments[2]);
this.options = _.defaults(resultOptions || {}, this.options || {}, Redis._defaultOptions);
if (this.options.parser === 'javascript') {
this.Parser = require('./parsers/javascript');
this.Parser = JavaScriptParser;
} else {
try {
this.Parser = require('./parsers/hiredis');
} catch (e) {
if (this.options.parser === 'hiredis') {
throw e;
}
this.Parser = require('./parsers/javascript');
}
this.Parser = NativeParser || JavaScriptParser;
}

@@ -146,3 +112,5 @@

if (this.options.sentinels) {
this._sentinel = new Sentinel(this.options.sentinels, this.options.role, this.options.name);
this.connector = new SentinelConnector(this.options);
} else {
this.connector = new Connector(this.options);
}

@@ -152,7 +120,6 @@

// disconnected(or inactive) -> connected -> ready -> closing -> closed
// end(or wait) -> connecting -> connect -> ready -> end
if (this.options.lazyConnect) {
this.status = 'inactive';
this.setStatus('wait');
} else {
this.status = 'disconnected';
this.connect();

@@ -177,28 +144,66 @@ }

*
* @var _defaultOptions
* @private
* @var defaultOptions
* @protected
*/
Redis._defaultOptions = {
Redis.defaultOptions = {
// Connection
port: 6379,
host: 'localhost',
family: 4,
enableOfflineQueue: true,
enableReadyCheck: true,
connectTimeout: 3000,
retryStrategy: function (times) {
var delay = Math.min(times * 2, 2000);
return delay;
return Math.min(times * 2, 2000);
},
autoResubscribe: true,
parser: 'auto',
lazyConnect: false,
// Sentinel
sentinels: null,
name: null,
role: 'master',
sentinelRetryStrategy: function (times) {
return Math.min(times * 10, 1000);
},
// Status
password: null,
db: 0,
role: 'master',
sentinel: null,
roleRetryDelay: 500,
connectTimeout: 10000,
name: null
// Others
parser: 'auto',
enableOfflineQueue: true,
enableReadyCheck: true,
autoResubscribe: true,
lazyConnect: false
};
Redis.prototype.parseOptions = function () {
this.options = {};
for (var i = 0; i < arguments.length; ++i) {
var arg = arguments[i];
if (arg === null || typeof arg === 'undefined') {
continue;
}
if (typeof arg === 'object') {
_.defaults(this.options, arg);
} else if (typeof arg === 'string') {
_.defaults(this.options, utils.parseURL(arg));
} else if (typeof arg === 'number') {
this.options.port = arg;
} else {
throw new Error('Invalid argument ' + arg);
}
}
_.defaults(this.options, Redis.defaultOptions);
if (typeof this.options.port === 'string') {
this.options.port = parseInt(this.options.port, 10);
}
if (typeof this.options.db === 'string') {
this.options.db = parseInt(this.options.db, 10);
}
};
Redis.prototype.setStatus = function (status) {
debug('status[%s:%s]: %s -> %s', this.options.host, this.options.port, this.status || '[empty]', status);
this.status = status;
process.nextTick(this.emit.bind(this, status));
};
/**

@@ -210,2 +215,7 @@ * Create a connection to Redis.

Redis.prototype.connect = function () {
if (this.status === 'connecting' || this.status === 'connect') {
return false;
}
this.setStatus('connecting');
this.condition = {

@@ -220,41 +230,28 @@ select: this.options.db,

if (this._sentinel) {
var _this = this;
this._sentinel.removeAllListeners();
this._sentinel.once('connect', function (connection) {
debug('received connection from sentinel');
_this.connection = connection;
bindEvent(_this);
});
this._sentinel.on('error', function (error) {
_this.silentEmit('error', error);
});
this._sentinel.connect(this.options.role);
} else {
var connectionOptions;
if (this.options.path) {
connectionOptions = _.pick(this.options, ['path']);
} else {
connectionOptions = _.pick(this.options, ['port', 'host', 'family']);
var _this = this;
this.connector.connect(function (err, stream) {
if (err) {
_this.flushQueue(err);
return;
}
this.connection = net.createConnection(connectionOptions);
bindEvent(this);
}
function bindEvent(self) {
self.connection.once('connect', eventHandler.connectHandler(self));
self.connection.once('error', eventHandler.errorHandler(self));
self.connection.once('close', eventHandler.closeHandler(self));
self.connection.on('data', eventHandler.dataHandler(self));
if (self.options.connectTimeout) {
self.connection.setTimeout(self.options.connectTimeout, function () {
self.connection.setTimeout(0);
self.manuallyClosing = true;
self.connection.destroy();
_this.stream = stream;
stream.once('connect', eventHandler.connectHandler(_this));
stream.once('error', eventHandler.errorHandler(_this));
stream.once('close', eventHandler.closeHandler(_this));
stream.on('data', eventHandler.dataHandler(_this));
if (_this.options.connectTimeout) {
stream.setTimeout(_this.options.connectTimeout, function () {
stream.setTimeout(0);
_this.manuallyClosing = true;
stream.destroy();
});
self.connection.once('connect', function () {
self.connection.setTimeout(0);
stream.once('connect', function () {
stream.setTimeout(0);
});
}
}
});
return true;
};

@@ -266,16 +263,11 @@

* This method closes the connection immediately,
* and may lose some pending replies that haven't written to clien.
* and may lose some pending replies that haven't written to client.
* If you want to wait for the pending replies, use Redis#quit instead.
* @public
*/
Redis.prototype.disconnect = function (options) {
options = options || {};
this.manuallyClosing = !options.reconnect;
if (this.connection) {
this.connection.end();
Redis.prototype.disconnect = function (reconnect) {
if (!reconnect) {
this.manuallyClosing = true;
}
if (this._sentinel) {
this._sentinel.disconnect(options);
}
this.connector.disconnect();
};

@@ -289,3 +281,3 @@

Redis.prototype.end = function () {
this.disconnect.apply(this, arguments);
this.disconnect();
};

@@ -493,18 +485,19 @@

Redis.prototype.sendCommand = function (command, stream) {
if (this.status === 'inactive') {
this.status = 'disconnected';
if (this.status === 'wait') {
this.connect();
}
if (this.status === 'end') {
return command.reject(new Error('Connection is closed.'));
}
if (this.condition.mode.subscriber && !_.includes(Command.FLAGS.VALID_IN_SUBSCRIBER_MODE, command.name)) {
command.reject(new Error('Connection in subscriber mode, only subscriber commands may be used'));
return command.promise;
return command.reject(new Error('Connection in subscriber mode, only subscriber commands may be used'));
}
var writable = (this.status === 'ready') || ((this.status === 'connected') && _.includes(Command.FLAGS.VALID_WHEN_LOADING, command.name));
var writable = (this.status === 'ready') || ((this.status === 'connect') && _.includes(Command.FLAGS.VALID_WHEN_LOADING, command.name));
if (!stream) {
if (!this.connection) {
if (!this.stream) {
writable = false;
} else if (!this.connection.writable) {
} else if (!this.stream.writable) {
writable = false;
} else if (this.connection._writableState && this.connection._writableState.ended) {
} else if (this.stream._writableState && this.stream._writableState.ended) {
// https://github.com/iojs/io.js/pull/1217

@@ -515,5 +508,9 @@ writable = false;

if (!writable && !this.options.enableOfflineQueue) {
return command.reject(new Error('Stream isn\'t writeable and enableOfflineQueue options is false'));
}
if (writable) {
debug('write command[%d] -> %s(%s)', this.condition.select, command.name, command.args);
(stream || this.connection).write(command.toWritable());
(stream || this.stream).write(command.toWritable());

@@ -525,8 +522,2 @@ this.commandQueue.push(command);

}
if (command.name === 'select' && utils.isInt(command.args[0])) {
this.condition.select = parseInt(command.args[0], 10);
debug('switch to db [%d]', this.condition.select);
}
} else if (this.options.enableOfflineQueue) {

@@ -539,10 +530,9 @@ debug('queue command[%d] -> %s(%s)', this.condition.select, command.name, command.args);

});
if (command.name === 'select' && utils.isInt(command.args[0])) {
this.condition.select = parseInt(command.args[0], 10);
debug('switch to db [%d]', this.condition.select);
}
} else {
command.reject(new Error('Stream isn\'t writeable and enableOfflineQueue options is false'));
}
if (command.name === 'select' && utils.isInt(command.args[0])) {
this.condition.select = parseInt(command.args[0], 10);
debug('switch to db [%d]', this.condition.select);
}
return command.promise;

@@ -554,46 +544,1 @@ };

module.exports = Redis;
Redis.Command = Command;
Redis.Command.setArgumentTransformer('mset', function (args) {
if (args.length === 1) {
if (typeof Map !== 'undefined' && args[0] instanceof Map) {
return utils.convertMapToArray(args[0]);
}
if ( typeof args[0] === 'object' && args[0] !== null) {
return utils.convertObjectToArray(args[0]);
}
}
return args;
});
Redis.Command.setArgumentTransformer('hmset', function (args) {
if (args.length === 2) {
if (typeof Map !== 'undefined' && args[1] instanceof Map) {
return [args[0]].concat(utils.convertMapToArray(args[1]));
}
if ( typeof args[1] === 'object' && args[1] !== null) {
return [args[0]].concat(utils.convertObjectToArray(args[1]));
}
}
return args;
});
Redis.Command.setReplyTransformer('hgetall', function (result) {
if (Array.isArray(result)) {
var obj = {};
for (var i = 0; i < result.length; i += 2) {
obj[result[i]] = result[i + 1];
}
return obj;
}
return result;
});
Redis.Cluster = require('./redis_cluster');
Redis.ReplyError = require('./reply_error');
Redis.Promise = Promise;
var Sentinel = require('./sentinel');

@@ -7,5 +7,3 @@ 'use strict';

return function () {
debug('status: %s -> connected', self.status);
self.status = 'connected';
self.emit('connect');
self.setStatus('connect');

@@ -17,11 +15,4 @@ self.commandQueue = [];

self.auth(self.condition.auth, function (err, res) {
if (res === 'OK') {
return;
}
if (err.message.match('no password is set')) {
if (err && err.message.match('no password is set')) {
console.warn('`auth` is specified in the client but not in the server.');
} else if (err) {
self.silentEmit('error', new Error('Auth error: ' + err.message));
} else {
self.silentEmit('error', new Error('Auth failed: ' + res));
}

@@ -35,4 +26,3 @@ });

// TODO cross file calling private method
self._initParser();
self.initParser();

@@ -43,15 +33,10 @@ if (self.options.enableReadyCheck) {

self.flushQueue(new Error('Ready check failed: ' + err.message));
return;
} else {
self.serverInfo = info;
if (self.connector.check(info)) {
exports.readyHandler(self)();
} else {
self.disconnect(true);
}
}
if (self._sentinel && info.role && self.options.role !== info.role) {
debug('role invalid, expected %s, but got %s', self.options.role, info.role);
self.disconnect();
setTimeout(function () {
self.connect();
}, self.options.roleRetryDelay);
return;
}
self.serverInfo = info;
exports.readyHandler(self)();
});

@@ -64,3 +49,4 @@ }

return function () {
debug('status: %s -> closed', self.status);
self.setStatus('end');
self.prevCondition = self.condition;

@@ -87,6 +73,4 @@

self.setStatus('reconnecting');
setTimeout(function () {
self.status = 'reconnecting';
self.emit('reconnecting');
self.connect();

@@ -98,4 +82,2 @@ }, retryDelay);

self.flushQueue(new Error('Connection is closed.'));
self.status = 'closed';
self.emit('close');
}

@@ -119,4 +101,3 @@ };

return function () {
debug('status: %s -> ready', self.status);
self.status = 'ready';
self.setStatus('ready');
self.retryAttempts = 0;

@@ -168,4 +149,3 @@

}
self.emit('ready');
};
};

@@ -15,3 +15,3 @@ 'use strict';

*/
exports._initParser = function () {
exports.initParser = function () {
var self = this;

@@ -154,3 +154,3 @@

if (--command.remainReplies === 0) {
command.resolve(reply[2]);
command.resolve(count);
return true;

@@ -157,0 +157,0 @@ }

'use strict';
var util = require('util');
var utils = require('./utils');
function ReplyError(message) {
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.message = message;
}
// inherit from Error
util.inherits(ReplyError, Error);
module.exports = ReplyError;
module.exports = utils.extendsError('ReplyError');
'use strict';
var urllib = require('url');
var util = require('util');
var _ = require('lodash');

@@ -250,1 +253,49 @@ /**

};
exports.parseURL = function (url) {
if (exports.isInt(url)) {
return { port: url };
}
var parsed = urllib.parse(url, true, true);
if (!parsed.slashes && url[0] !== '/') {
url = '//' + url;
parsed = urllib.parse(url, true, true);
}
var result = {};
if (parsed.auth) {
result.password = parsed.auth.split(':')[1];
}
if (parsed.pathname) {
if (parsed.protocol === 'redis:') {
result.db = parsed.pathname.slice(1);
} else {
result.path = parsed.pathname;
}
}
if (parsed.host) {
result.host = parsed.hostname;
}
if (parsed.port) {
result.port = parsed.port;
}
_.defaults(result, parsed.query);
return result;
};
exports.extendsError = function (name) {
var errorClass = function (message) {
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.name = name;
this.message = message;
};
// inherit from Error
util.inherits(errorClass, Error);
return errorClass;
};
{
"name": "ioredis",
"version": "1.0.13",
"version": "1.1.0",
"description": "A delightful, performance-focused Redis client for Node and io.js",

@@ -10,3 +10,3 @@ "main": "index.js",

"generate-docs": "jsdoc2md lib/redis.js lib/redis_cluster.js lib/commander.js > API.md",
"bench": "matcha benchmark.js"
"bench": "matcha benchmarks/*.js"
},

@@ -19,6 +19,5 @@ "repository": {

"redis",
"node",
"io",
"cluster",
"sentinel"
"sentinel",
"pipelining"
],

@@ -25,0 +24,0 @@ "author": "luin <i@zihua.li> (http://zihua.li)",

@@ -5,2 +5,3 @@ # ioredis

[![Test Coverage](https://codeclimate.com/github/luin/ioredis/badges/coverage.svg)](https://codeclimate.com/github/luin/ioredis)
[![Code Climate](https://codeclimate.com/github/luin/ioredis/badges/gpa.svg)](https://codeclimate.com/github/luin/ioredis)
[![Dependency Status](https://david-dm.org/luin/ioredis.svg)](https://david-dm.org/luin/ioredis)

@@ -90,3 +91,4 @@ [![Join the chat at https://gitter.im/luin/ioredis](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/luin/ioredis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

Here is a simple example of the API for publish / subscribe.
This program opens two client connections. It subscribes to a channel with one connection,
The following program opens two client connections.
It subscribes to a channel with one connection,
and publishes to that channel with the other:

@@ -119,2 +121,10 @@

`PSUBSCRIBE` is also supported in a similar way:
```javascript
redis.psubscribe('pat?ern', function (err, count) {});
redis.on('pmessage', function (pattern, channel, message) {});
redis.on('pmessageBuffer', function (pattern, channel, message) {});
```
When a client issues a SUBSCRIBE or PSUBSCRIBE, that connection is put into a "subscriber" mode.

@@ -377,2 +387,15 @@ At that point, only commands that modify the subscription set are valid.

## Connection Events
Redis instance will emit some events about the state of the connection to the Redis server.
### "connect"
client will emit `connect` once a connection is established to the Redis server.
### "ready"
If `enableReadyCheck` is `true`, client will emit `ready` when the server reports that it is ready to receive commands.
Otherwise `ready` will be emitted immediately right after the `connect` event.
### "end"
client will emit `end` when an established Redis server connection has closed.
## Offline Queue

@@ -412,8 +435,15 @@ When a command can't be processed by Redis(e.g. the connection hasn't been established or

Besides `retryStrategy` option, there's also a `sentinelRetryStrategy` in Sentinel mode which will be invoked when all the sentinel nodes are unreachable during connecting. If `sentinelRetryStrategy` returns a valid delay time, ioredis will try to reconnect from scratch. The default value of `sentinelRetryStrategy` is:
```javascript
function (times) {
var delay = Math.min(times * 10, 1000);
return delay;
}
```
## Cluster
Support for Cluster is currently experimental and under active development. It's not recommended to use it in production.
If you encounter any problems, welcome to submit an issue :-).
Redis Cluster provides a way to run a Redis installation where data is automatically sharded across multiple Redis nodes.
You can connect to a Redis Cluster like this:
You can connect to a cluster like this:
```javascript

@@ -435,9 +465,29 @@ var Redis = require('ioredis');

```
When using `Redis.Cluster` to connect to a cluster, there are some differences from using `Redis`:
0. The argument is a list of nodes of the cluster you want to connect.
`Cluster` constructor accepts two arguments, where:
0. The first argument is a list of nodes of the cluster you want to connect to.
Just like Sentinel, the list does not need to enumerate all your cluster nodes,
but a few so that if one is down the client will try the next one, and the client will discover other nodes automatically when at least one node is connnected.
0. Pipelining is not available in the cluster currently.
but a few so that if one is unreachable the client will try the next one, and the client will discover other nodes automatically when at least one node is connnected.
0. The second argument is the option that will be passed to the `Redis` constructor when creating connections to Redis nodes internally. There are some additional options:
0. `clusterRetryStrategy`: When none of the startup nodes are reachable, `clusterRetryStrategy` will be invoked. When a number is returned,
ioredis will try to reconnect the startup nodes from scratch after the specified delay(ms). Otherwise an error of "None of startup nodes is available" will returned.
The default value of this option is:
```javascript
function (times) {
var delay = Math.min(100 + times * 2, 2000);
return delay;
}
```
0. `refreshAfterFails`: When `MOVED` errors are received more times than `refreshAfterFails`, client will call CLUSTER SLOTS
command to refresh the slot cache. The default value is `4`.
0. `maxRedirections`: When a `MOVED` or `ASK` error is received, client will redirect the
command to another node. This option limits the max redirections allowed when sending a command. The default value is `16`.
0. `retryDelayOnFailover`: When the error of "Connection is closed." is received when sending a command,
ioredis will retry after the specified delay. The default value is `2000`. You should make sure to let `retryDelayOnFailover * maxRedirections > cluster-node-timeout`
in order to insure that no command will fails during a failover.
Currently pipeline isn't supported in the Cluster mode.
## hiredis

@@ -554,3 +604,3 @@ If [hiredis](https://github.com/redis/hiredis-node) is installed(by `npm install hiredis`),

You can find the code at `benchmark.js` and run it yourself using `npm run bench`.
You can find the code at `benchmarks/*.js` and run it yourself using `npm run bench`.

@@ -557,0 +607,0 @@ # Running tests

@@ -8,2 +8,73 @@ 'use strict';

var Redis = require('./');
// var redis = new Redis();
// redis.disconnect();
// setTimeout(function () {
// redis.get('foo', function () {
// console.log(arguments);
// });
// }, 1000);
// setTimeout(function () {
// }, 100000);
// redis.on('error', function () {
// console.log('error', arguments);
// });
// redis.on('timeout', function () {
// console.log('timeout', arguments);
// });
// redis.on('close', function () {
// console.log('close', arguments);
// });
// redis.on('connect', function () {
// console.log('connect', arguments);
// });
// var redis = new Redis();
var redis = new Redis({
sentinels: [
{ host: 'localhost', port: 26379 }
],
name: 'master1'
});
redis.set('foo', 'bar');
setInterval(function () {
redis.get('foo', console.log);
}, 1000);
// setTimeout(function () {
// console.log('start');
// var pending = 0;
// for (var i = 0; i < 3000; ++i) {
// pending += 1;
// doo(function () {
// if (!--pending) {
// console.log('done');
// }
// });
// }
// }, 10000);
// function doo(callback) {
// var pending = 0;
// for (var j = 0; j < 50; ++j) {
// pending += 1;
// redis.set('foo', 'bar', function () {
// if (!--pending) {
// callback();
// }
// });
// }
// }
// setInterval(function () {
// redis.get('foo', function () {
// console.log(arguments);
// });
// }, 2000);
// var redis = new Redis({ showFriendlyErrorStack: 10 });

@@ -53,24 +124,40 @@ // var redis = new Redis();

var cluster = new Redis.Cluster([{
port: 6380,
host: '127.0.0.1'
}, {
port: 6381,
host: '127.0.0.1'
}]);
// var cluster = new Redis.Cluster([{
// port: 6380,
// host: '127.0.0.1'
// }, {
// port: 6381,
// host: '127.0.0.1'
// }], { showFriendlyErrorStack: true });
cluster.defineCommand('testGet', {
numberOfKeys: 1,
lua: 'return redis.call("mget", KEYS[1], ARGV[1])'
});
cluster.testGet('foo', '{fo}2', function (err, res) {
console.log(err, res);
});
// var redis = new Redis(6388, '177.22.22.2', {
// enableReadyCheck: false
// setTimeout(function () {
// // cluster.disconnect();
// }, 2000);
// cluster.get('foo3', function (err, res) {
// console.log(err, res);
// });
// redis.on('end', function (err) {
// console.log(err)
// cluster.defineCommand('testGet', {
// numberOfKeys: 1,
// lua: 'return redis.call("mget", KEYS[1], ARGV[1])'
// });
// cluster.set('foo', 'bar');
// cluster.set('foo2', 'bar1');
// cluster.set('{foo}2', 'bar2');
// setInterval(function () {
// cluster.testGet('foo', '{foo}2', console.log);
// }, 1000);
// // cluster.testGet('foo', '{fo}2', function (err, res) {
// // console.log(err, res);
// // });
// // var redis = new Redis(6388, '177.22.22.2', {
// // enableReadyCheck: false
// // });
// // redis.on('end', function (err) {
// // console.log(err)
// // });
// redis.disconnect();

@@ -77,0 +164,0 @@ // var redis = new Redis({

@@ -43,2 +43,17 @@ 'use strict';

});
it('should warn when the server doesn\'t need auth', function (done) {
stub(console, 'warn', function () {
console.warn.restore();
redis.disconnect();
server.disconnect();
done();
});
var server = new MockServer(17379, function (argv) {
if (argv[0] === 'auth' && argv[1] === 'pass') {
return new Error('ERR Client sent AUTH, but no password is set');
}
});
var redis = new Redis({ port: 17379, password: 'pass' });
});
});

@@ -7,2 +7,36 @@ 'use strict';

describe('connect', function () {
it('should flush the queue when all startup nodes are unreachable', function (done) {
var cluster = new Redis.Cluster([
{ host: '127.0.0.1', port: '30001' }
], { clusterRetryStrategy: null });
cluster.get('foo', function (err) {
expect(err.message).to.match(/None of startup nodes is available/);
cluster.disconnect();
done();
});
});
it('should invoke clusterRetryStrategy when all startup nodes are unreachable', function (done) {
var t = 0;
var cluster = new Redis.Cluster([
{ host: '127.0.0.1', port: '30001' },
{ host: '127.0.0.1', port: '30002' }
], {
clusterRetryStrategy: function (times) {
expect(times).to.eql(++t);
if (times === 3) {
return;
}
return 0;
}
});
cluster.get('foo', function (err) {
expect(t).to.eql(3);
expect(err.message).to.match(/None of startup nodes is available/);
done();
});
});
it('should connect to cluster successfully', function (done) {

@@ -51,3 +85,3 @@ var node = new MockServer(30001);

it('should send command the the correct node', function (done) {
it('should send command to the correct node', function (done) {
var node1 = new MockServer(30001, function (argv) {

@@ -63,4 +97,6 @@ if (argv[0] === 'cluster' && argv[1] === 'slots') {

if (argv[0] === 'get' && argv[1] === 'foo') {
cluster.disconnect();
disconnect([node1, node2], done);
process.nextTick(function () {
cluster.disconnect();
disconnect([node1, node2], done);
});
}

@@ -89,4 +125,6 @@ });

expect(moved).to.eql(true);
cluster.disconnect();
disconnect([node1, node2], done);
process.nextTick(function () {
cluster.disconnect();
disconnect([node1, node2], done);
});
}

@@ -131,4 +169,6 @@ }

if (++times === 2) {
cluster.disconnect();
disconnect([node1, node2], done);
process.nextTick(function () {
cluster.disconnect();
disconnect([node1, node2], done);
});
} else {

@@ -148,2 +188,86 @@ return new Error('ASK ' + utils.calcSlot('foo') + ' 127.0.0.1:30001');

});
describe('maxRedirections', function () {
it('should return error when reached max redirection', function (done) {
var redirectTimes = 0;
var argvHandler = function (argv) {
if (argv[0] === 'cluster' && argv[1] === 'slots') {
return [
[0, 1, ['127.0.0.1', 30001]],
[2, 16383, ['127.0.0.1', 30002]]
];
} else if (argv[0] === 'get' && argv[1] === 'foo') {
redirectTimes += 1;
return new Error('ASK ' + utils.calcSlot('foo') + ' 127.0.0.1:30001');
}
};
var node1 = new MockServer(30001, argvHandler);
var node2 = new MockServer(30002, argvHandler);
var cluster = new Redis.Cluster([
{ host: '127.0.0.1', port: '30001' }
], { maxRedirections: 5 });
cluster.get('foo', function (err) {
expect(redirectTimes).to.eql(5);
expect(err.message).to.match(/Too many Cluster redirections/);
cluster.disconnect();
disconnect([node1, node2], done);
});
});
});
describe('refreshAfterFails', function () {
it('should re-fetch slots when reached refreshAfterFails', function (done) {
var redirectTimes = 0;
var argvHandler = function (argv) {
if (argv[0] === 'cluster' && argv[1] === 'slots') {
if (redirectTimes === 4) {
cluster.disconnect();
disconnect([node1, node2], done);
}
return [
[0, 1, ['127.0.0.1', 30001]],
[2, 16383, ['127.0.0.1', 30002]]
];
} else if (argv[0] === 'get' && argv[1] === 'foo') {
if (redirectTimes < 4) {
redirectTimes += 1;
return new Error('MOVED ' + utils.calcSlot('foo') + ' 127.0.0.1:30001');
}
}
};
var node1 = new MockServer(30001, argvHandler);
var node2 = new MockServer(30002, argvHandler);
var cluster = new Redis.Cluster([
{ host: '127.0.0.1', port: '30001' }
], { refreshAfterFails: 4 });
cluster.get('foo');
});
});
it('should get value successfully', function (done) {
var node1 = new MockServer(30001, function (argv) {
if (argv[0] === 'cluster' && argv[1] === 'slots') {
return [
[0, 1, ['127.0.0.1', 30001]],
[2, 16383, ['127.0.0.1', 30002]]
];
}
});
var node2 = new MockServer(30002, function (argv) {
if (argv[0] === 'get' && argv[1] === 'foo') {
return 'bar';
}
});
var cluster = new Redis.Cluster([
{ host: '127.0.0.1', port: '30001' }
]);
cluster.get('foo', function (err, result) {
expect(result).to.eql('bar');
cluster.disconnect();
disconnect([node1, node2], done);
});
});
});

@@ -150,0 +274,0 @@

@@ -14,4 +14,6 @@ 'use strict';

var redis = new Redis();
redis.once('close', done);
redis.disconnect();
redis.once('end', done);
redis.once('connect', function () {
redis.disconnect();
});
});

@@ -37,3 +39,3 @@

redis.set('foo', 'bar', function () {
redis.connection.end();
redis.stream.end();
});

@@ -56,8 +58,40 @@ redis.get('foo', function (err, res) {

var redis = new Redis({ connectTimeout: 10000 });
stub(redis.connection, 'setTimeout', function (timeout) {
expect(timeout).to.eql(0);
redis.connection.setTimeout.restore();
done();
setImmediate(function () {
stub(redis.stream, 'setTimeout', function (timeout) {
expect(timeout).to.eql(0);
redis.stream.setTimeout.restore();
done();
});
});
});
describe('retryStrategy', function () {
it('should pass the correct retry times', function (done) {
var t = 0;
new Redis({
port: 1,
retryStrategy: function (times) {
expect(times).to.eql(++t);
if (times === 3) {
done();
return;
}
return 0;
}
});
});
it('should skip reconnecting when retryStrategy doesn\'t return a number', function (done) {
var redis = new Redis({
port: 1,
retryStrategy: function () {
process.nextTick(function () {
expect(redis.status).to.eql('end');
done();
});
return null;
}
});
});
});
});

@@ -135,3 +135,3 @@ 'use strict';

it('should able to send quit command in the subscriber mode', function (done) {
it('should be able to send quit command in the subscriber mode', function (done) {
var redis = new Redis();

@@ -144,3 +144,3 @@ var pending = 1;

});
redis.on('close', function () {
redis.on('end', function () {
expect(pending).to.eql(0);

@@ -152,3 +152,3 @@ redis.disconnect();

it('should restore subscription after reconnecting', function (done) {
it('should restore subscription after reconnecting(subscribe)', function (done) {
var redis = new Redis();

@@ -172,2 +172,22 @@ var pub = new Redis();

});
it('should restore subscription after reconnecting(psubscribe)', function (done) {
var redis = new Redis();
var pub = new Redis();
redis.psubscribe('fo?o', 'ba?r', function () {
redis.on('ready', function () {
var pending = 2;
redis.on('pmessage', function (pattern, channel, message) {
if (!--pending) {
redis.disconnect();
pub.disconnect();
done();
}
});
pub.publish('fo1o', 'hi1');
pub.publish('ba1r', 'hi2');
});
redis.disconnect({ reconnect: true });
});
});
});

@@ -18,3 +18,3 @@ 'use strict';

redis.set('foo', '2', function () {
redis.connection.destroy();
redis.stream.destroy();
redis.select('3');

@@ -42,3 +42,3 @@ redis.set('foo', '3');

redis.set('foo', '2', function () {
redis.connection.destroy();
redis.stream.destroy();
redis.get('foo', function (err, res) {

@@ -45,0 +45,0 @@ expect(res).to.eql('2');

@@ -37,3 +37,4 @@ 'use strict';

it('should raise error when all sentinel are unreachable', function (done) {
it('should call sentinelRetryStrategy when all sentinels are unreachable', function (done) {
var t = 0;
var redis = new Redis({

@@ -44,13 +45,16 @@ sentinels: [

],
sentinelRetryStrategy: function (times) {
expect(times).to.eql(++t);
var sentinel = new MockServer(27380);
sentinel.once('connect', function () {
redis.disconnect();
sentinel.disconnect(done);
});
return 0;
},
name: 'master'
});
redis.once('error', function (error) {
redis.disconnect();
expect(error.message).to.match(/are unreachable/);
done();
});
});
it('should continue trying when all sentinels are unreachable', function (done) {
it('should raise error when all sentinel are unreachable and retry is disabled', function (done) {
var redis = new Redis({

@@ -61,15 +65,14 @@ sentinels: [

],
sentinelRetryStrategy: null,
name: 'master'
});
redis.once('error', function (err) {
var sentinel = new MockServer(27380);
sentinel.once('connect', function () {
redis.disconnect();
sentinel.disconnect(done);
});
redis.get('foo', function (error) {
expect(error.message).to.match(/are unreachable/);
redis.disconnect();
done();
});
});
it('should also close the connect to the sentinel when disconnect', function (done) {
it('should close the connection to the sentinel when resolving successfully', function (done) {
var sentinel = new MockServer(27379, function (argv) {

@@ -82,2 +85,3 @@ if (argv[0] === 'sentinel' && argv[1] === 'get-master-addr-by-name') {

sentinel.once('disconnect', function () {
redis.disconnect();
master.disconnect(function () {

@@ -94,3 +98,2 @@ sentinel.disconnect(done);

});
redis.disconnect();
});

@@ -174,4 +177,3 @@ });

],
name: 'master',
roleRetryDelay: 0
name: 'master'
});

@@ -259,4 +261,3 @@ });

name: 'master',
role: 'slave',
roleRetryDelay: 0
role: 'slave'
});

@@ -263,0 +264,0 @@ });

@@ -0,1 +1,3 @@

'use strict';
var net = require('net');

@@ -13,3 +15,5 @@ var util = require('util');

this.socket = net.createServer(function (c) {
_this.emit('connect', c);
process.nextTick(function () {
_this.emit('connect', c);
});

@@ -16,0 +20,0 @@ var parser = new Parser({ returnBuffer: false });

@@ -54,2 +54,24 @@ 'use strict';

});
describe('#getKeys()', function () {
it('should return keys', function () {
expect(getKeys('get', ['foo'])).to.eql(['foo']);
expect(getKeys('mget', ['foo', 'bar'])).to.eql(['foo', 'bar']);
expect(getKeys('mset', ['foo', 'v1', 'bar', 'v2'])).to.eql(['foo', 'bar']);
expect(getKeys('hmset', ['key', 'foo', 'v1', 'bar', 'v2'])).to.eql(['key']);
expect(getKeys('blpop', ['key1', 'key2', '17'])).to.eql(['key1', 'key2']);
expect(getKeys('evalsha', ['23123', '2', 'foo', 'bar', 'zoo'])).to.eql(['foo', 'bar']);
expect(getKeys('evalsha', ['23123', 2, 'foo', 'bar', 'zoo'])).to.eql(['foo', 'bar']);
expect(getKeys('sort', ['key'])).to.eql(['key']);
expect(getKeys('sort', ['key', 'BY', 'hash:*->field'])).to.eql(['key', 'hash:*']);
expect(getKeys('sort', ['key', 'BY', 'hash:*->field', 'LIMIT', 2, 3, 'GET', 'gk', 'GET', '#', 'Get', 'gh->f*', 'DESC', 'ALPHA', 'STORE', 'store'])).to.eql(['key', 'hash:*', 'gk', 'gh->f', 'store']);
expect(getKeys('zunionstore', ['out', 2, 'zset1', 'zset2', 'WEIGHTS', 2, 3])).to.eql(['out', 'zset1', 'zset2']);
expect(getKeys('zinterstore', ['out', 2, 'zset1', 'zset2', 'WEIGHTS', 2, 3])).to.eql(['out', 'zset1', 'zset2']);
function getKeys(commandName, args) {
var command = new Command(commandName, args);
return command.getKeys();
}
});
});
});

@@ -126,2 +126,27 @@ 'use strict';

});
describe('.parseURL', function () {
it('should return correctly', function () {
expect(utils.parseURL('/tmp.sock')).to.eql({ path: '/tmp.sock' });
expect(utils.parseURL('127.0.0.1')).to.eql({ host: '127.0.0.1' });
expect(utils.parseURL('6379')).to.eql({ port: '6379' });
expect(utils.parseURL('127.0.0.1:6379')).to.eql({
host: '127.0.0.1',
port: '6379'
});
expect(utils.parseURL('127.0.0.1:6379?db=2&key=value')).to.eql({
host: '127.0.0.1',
port: '6379',
db: '2',
key: 'value'
});
expect(utils.parseURL('redis://user:pass@127.0.0.1:6380/4?key=value')).to.eql({
host: '127.0.0.1',
port: '6380',
db: '4',
password: 'pass',
key: 'value'
});
});
});
});

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc