2016-11-29 18:29:50 -08:00
'use strict' ;
var net = require ( 'net' ) ;
var tls = require ( 'tls' ) ;
var util = require ( 'util' ) ;
var utils = require ( './lib/utils' ) ;
var Command = require ( './lib/command' ) ;
var Queue = require ( 'double-ended-queue' ) ;
var errorClasses = require ( './lib/customErrors' ) ;
var EventEmitter = require ( 'events' ) ;
var Parser = require ( 'redis-parser' ) ;
var commands = require ( 'redis-commands' ) ;
var debug = require ( './lib/debug' ) ;
var unifyOptions = require ( './lib/createClient' ) ;
var SUBSCRIBE _COMMANDS = {
subscribe : true ,
unsubscribe : true ,
psubscribe : true ,
punsubscribe : true
} ;
// Newer Node.js versions > 0.10 return the EventEmitter right away and using .EventEmitter was deprecated
if ( typeof EventEmitter !== 'function' ) {
EventEmitter = EventEmitter . EventEmitter ;
}
function noop ( ) { }
function handle _detect _buffers _reply ( reply , command , buffer _args ) {
if ( buffer _args === false || this . message _buffers ) {
// If detect_buffers option was specified, then the reply from the parser will be a buffer.
// If this command did not use Buffer arguments, then convert the reply to Strings here.
reply = utils . reply _to _strings ( reply ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
if ( command === 'hgetall' ) {
reply = utils . reply _to _object ( reply ) ;
}
return reply ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
exports . debug _mode = /\bredis\b/i . test ( process . env . NODE _DEBUG ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
// Attention: The second parameter might be removed at will and is not officially supported.
// Do not rely on this
function RedisClient ( options , stream ) {
// Copy the options so they are not mutated
options = utils . clone ( options ) ;
EventEmitter . call ( this ) ;
var cnx _options = { } ;
var self = this ;
/* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
for ( var tls _option in options . tls ) {
cnx _options [ tls _option ] = options . tls [ tls _option ] ;
// Copy the tls options into the general options to make sure the address is set right
if ( tls _option === 'port' || tls _option === 'host' || tls _option === 'path' || tls _option === 'family' ) {
options [ tls _option ] = options . tls [ tls _option ] ;
}
}
if ( stream ) {
// The stream from the outside is used so no connection from this side is triggered but from the server this client should talk to
// Reconnect etc won't work with this. This requires monkey patching to work, so it is not officially supported
options . stream = stream ;
this . address = '"Private stream"' ;
} else if ( options . path ) {
cnx _options . path = options . path ;
this . address = options . path ;
} else {
cnx _options . port = + options . port || 6379 ;
cnx _options . host = options . host || '127.0.0.1' ;
cnx _options . family = ( ! options . family && net . isIP ( cnx _options . host ) ) || ( options . family === 'IPv6' ? 6 : 4 ) ;
this . address = cnx _options . host + ':' + cnx _options . port ;
}
// Warn on misusing deprecated functions
if ( typeof options . retry _strategy === 'function' ) {
if ( 'max_attempts' in options ) {
self . warn ( 'WARNING: You activated the retry_strategy and max_attempts at the same time. This is not possible and max_attempts will be ignored.' ) ;
// Do not print deprecation warnings twice
delete options . max _attempts ;
}
if ( 'retry_max_delay' in options ) {
self . warn ( 'WARNING: You activated the retry_strategy and retry_max_delay at the same time. This is not possible and retry_max_delay will be ignored.' ) ;
// Do not print deprecation warnings twice
delete options . retry _max _delay ;
}
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
this . connection _options = cnx _options ;
this . connection _id = RedisClient . connection _id ++ ;
2012-01-02 18:22:06 -08:00
this . connected = false ;
this . ready = false ;
2016-11-29 18:29:50 -08:00
if ( options . socket _nodelay === undefined ) {
options . socket _nodelay = true ;
} else if ( ! options . socket _nodelay ) { // Only warn users with this set to false
self . warn (
'socket_nodelay is deprecated and will be removed in v.3.0.0.\n' +
'Setting socket_nodelay to false likely results in a reduced throughput. Please use .batch for pipelining instead.\n' +
'If you are sure you rely on the NAGLE-algorithm you can activate it by calling client.stream.setNoDelay(false) instead.'
) ;
}
if ( options . socket _keepalive === undefined ) {
options . socket _keepalive = true ;
}
for ( var command in options . rename _commands ) {
options . rename _commands [ command . toLowerCase ( ) ] = options . rename _commands [ command ] ;
}
options . return _buffers = ! ! options . return _buffers ;
options . detect _buffers = ! ! options . detect _buffers ;
// Override the detect_buffers setting if return_buffers is active and print a warning
if ( options . return _buffers && options . detect _buffers ) {
self . warn ( 'WARNING: You activated return_buffers and detect_buffers at the same time. The return value is always going to be a buffer.' ) ;
options . detect _buffers = false ;
}
if ( options . detect _buffers ) {
// We only need to look at the arguments if we do not know what we have to return
this . handle _reply = handle _detect _buffers _reply ;
}
2012-01-02 18:22:06 -08:00
this . should _buffer = false ;
2016-11-29 18:29:50 -08:00
this . max _attempts = options . max _attempts | 0 ;
if ( 'max_attempts' in options ) {
self . warn (
'max_attempts is deprecated and will be removed in v.3.0.0.\n' +
2019-04-13 16:01:51 -04:00
'To reduce the number of options and to improve the reconnection handling please use the new `retry_strategy` option instead.\n' +
2016-11-29 18:29:50 -08:00
'This replaces the max_attempts and retry_max_delay option.'
) ;
}
this . command _queue = new Queue ( ) ; // Holds sent commands to de-pipeline them
this . offline _queue = new Queue ( ) ; // Holds commands issued but not able to be sent
this . pipeline _queue = new Queue ( ) ; // Holds all pipelined commands
// ATTENTION: connect_timeout should change in v.3.0 so it does not count towards ending reconnection attempts after x seconds
// This should be done by the retry_strategy. Instead it should only be the timeout for connecting to redis
this . connect _timeout = + options . connect _timeout || 3600000 ; // 60 * 60 * 1000 ms
this . enable _offline _queue = options . enable _offline _queue === false ? false : true ;
this . retry _max _delay = + options . retry _max _delay || null ;
if ( 'retry_max_delay' in options ) {
self . warn (
'retry_max_delay is deprecated and will be removed in v.3.0.0.\n' +
'To reduce the amount of options and the improve the reconnection handling please use the new `retry_strategy` option instead.\n' +
'This replaces the max_attempts and retry_max_delay option.'
) ;
2012-01-02 18:22:06 -08:00
}
this . initialize _retry _vars ( ) ;
2016-11-29 18:29:50 -08:00
this . pub _sub _mode = 0 ;
this . subscription _set = { } ;
2012-01-02 18:22:06 -08:00
this . monitoring = false ;
2016-11-29 18:29:50 -08:00
this . message _buffers = false ;
2012-01-02 18:22:06 -08:00
this . closing = false ;
this . server _info = { } ;
2016-11-29 18:29:50 -08:00
this . auth _pass = options . auth _pass || options . password ;
this . selected _db = options . db ; // Save the selected db here, used when reconnecting
this . old _state = null ;
this . fire _strings = true ; // Determine if strings or buffers should be written to the stream
this . pipeline = false ;
this . sub _commands _left = 0 ;
this . times _connected = 0 ;
this . buffers = options . return _buffers || options . detect _buffers ;
this . options = options ;
this . reply = 'ON' ; // Returning replies is the default
this . create _stream ( ) ;
// The listeners will not be attached right away, so let's print the deprecation message while the listener is attached
this . on ( 'newListener' , function ( event ) {
if ( event === 'idle' ) {
this . warn (
'The idle event listener is deprecated and will likely be removed in v.3.0.0.\n' +
'If you rely on this feature please open a new ticket in node_redis with your use case'
) ;
} else if ( event === 'drain' ) {
this . warn (
'The drain event listener is deprecated and will be removed in v.3.0.0.\n' +
'If you want to keep on listening to this event please listen to the stream drain event directly.'
) ;
2019-04-13 16:01:51 -04:00
} else if ( ( event === 'message_buffer' || event === 'pmessage_buffer' || event === 'messageBuffer' || event === 'pmessageBuffer' ) && ! this . buffers && ! this . message _buffers ) {
if ( this . reply _parser . name !== 'javascript' ) {
return this . warn (
'You attached the "' + event + '" listener without the returnBuffers option set to true.\n' +
'Please use the JavaScript parser or set the returnBuffers option to true to return buffers.'
) ;
}
this . reply _parser . optionReturnBuffers = true ;
2016-11-29 18:29:50 -08:00
this . message _buffers = true ;
this . handle _reply = handle _detect _buffers _reply ;
}
} ) ;
}
util . inherits ( RedisClient , EventEmitter ) ;
RedisClient . connection _id = 0 ;
function create _parser ( self ) {
return new Parser ( {
returnReply : function ( data ) {
self . return _reply ( data ) ;
} ,
returnError : function ( err ) {
// Return a ReplyError to indicate Redis returned an error
self . return _error ( err ) ;
} ,
returnFatalError : function ( err ) {
// Error out all fired commands. Otherwise they might rely on faulty data. We have to reconnect to get in a working state again
// Note: the execution order is important. First flush and emit, then create the stream
err . message += '. Please report this.' ;
self . ready = false ;
self . flush _and _error ( {
message : 'Fatal error encountert. Command aborted.' ,
code : 'NR_FATAL'
} , {
error : err ,
queues : [ 'command_queue' ]
} ) ;
self . emit ( 'error' , err ) ;
self . create _stream ( ) ;
} ,
returnBuffers : self . buffers || self . message _buffers ,
name : self . options . parser || 'javascript' ,
stringNumbers : self . options . string _numbers || false
} ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
All functions in here are internal besides the RedisClient constructor
and the exported functions . Don ' t rely on them as they will be private
functions in node _redis v . 3
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// Attention: the function name "create_stream" should not be changed, as other libraries need this to mock the stream (e.g. fakeredis)
RedisClient . prototype . create _stream = function ( ) {
2012-01-02 18:22:06 -08:00
var self = this ;
2019-04-13 16:01:51 -04:00
// Init parser
this . reply _parser = create _parser ( this ) ;
2016-11-29 18:29:50 -08:00
if ( this . options . stream ) {
// Only add the listeners once in case of a reconnect try (that won't work)
if ( this . stream ) {
return ;
}
this . stream = this . options . stream ;
} else {
// On a reconnect destroy the former stream and retry
if ( this . stream ) {
this . stream . removeAllListeners ( ) ;
this . stream . destroy ( ) ;
}
/* istanbul ignore if: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
if ( this . options . tls ) {
this . stream = tls . connect ( this . connection _options ) ;
} else {
this . stream = net . createConnection ( this . connection _options ) ;
}
}
if ( this . options . connect _timeout ) {
this . stream . setTimeout ( this . connect _timeout , function ( ) {
// Note: This is only tested if a internet connection is established
self . retry _totaltime = self . connect _timeout ;
self . connection _gone ( 'timeout' ) ;
} ) ;
}
/* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
var connect _event = this . options . tls ? 'secureConnect' : 'connect' ;
this . stream . once ( connect _event , function ( ) {
this . removeAllListeners ( 'timeout' ) ;
self . times _connected ++ ;
2012-01-02 18:22:06 -08:00
self . on _connect ( ) ;
} ) ;
2016-11-29 18:29:50 -08:00
this . stream . on ( 'data' , function ( buffer _from _socket ) {
// The buffer_from_socket.toString() has a significant impact on big chunks and therefore this should only be used if necessary
debug ( 'Net read ' + self . address + ' id ' + self . connection _id ) ; // + ': ' + buffer_from_socket.toString());
self . reply _parser . execute ( buffer _from _socket ) ;
self . emit _idle ( ) ;
2012-01-02 18:22:06 -08:00
} ) ;
2016-11-29 18:29:50 -08:00
this . stream . on ( 'error' , function ( err ) {
self . on _error ( err ) ;
2012-01-02 18:22:06 -08:00
} ) ;
2016-11-29 18:29:50 -08:00
/* istanbul ignore next: difficult to test and not important as long as we keep this listener */
this . stream . on ( 'clientError' , function ( err ) {
debug ( 'clientError occured' ) ;
self . on _error ( err ) ;
2012-01-02 18:22:06 -08:00
} ) ;
2016-11-29 18:29:50 -08:00
this . stream . once ( 'close' , function ( hadError ) {
self . connection _gone ( 'close' ) ;
2012-01-02 18:22:06 -08:00
} ) ;
2016-11-29 18:29:50 -08:00
this . stream . once ( 'end' , function ( ) {
self . connection _gone ( 'end' ) ;
2012-01-02 18:22:06 -08:00
} ) ;
2016-11-29 18:29:50 -08:00
this . stream . on ( 'drain' , function ( ) {
self . drain ( ) ;
} ) ;
if ( this . options . socket _nodelay ) {
this . stream . setNoDelay ( ) ;
}
// Fire the command before redis is connected to be sure it's the first fired command
if ( this . auth _pass !== undefined ) {
this . ready = true ;
2019-04-13 16:01:51 -04:00
// Fail silently as we might not be able to connect
this . auth ( this . auth _pass , function ( err ) {
if ( err && err . code !== 'UNCERTAIN_STATE' ) {
self . emit ( 'error' , err ) ;
}
} ) ;
2016-11-29 18:29:50 -08:00
this . ready = false ;
}
} ;
RedisClient . prototype . handle _reply = function ( reply , command ) {
if ( command === 'hgetall' ) {
reply = utils . reply _to _object ( reply ) ;
}
return reply ;
} ;
RedisClient . prototype . cork = noop ;
RedisClient . prototype . uncork = noop ;
2012-01-02 18:22:06 -08:00
RedisClient . prototype . initialize _retry _vars = function ( ) {
this . retry _timer = null ;
this . retry _totaltime = 0 ;
2016-11-29 18:29:50 -08:00
this . retry _delay = 200 ;
2012-01-02 18:22:06 -08:00
this . retry _backoff = 1.7 ;
this . attempts = 1 ;
} ;
2016-11-29 18:29:50 -08:00
RedisClient . prototype . warn = function ( msg ) {
var self = this ;
// Warn on the next tick. Otherwise no event listener can be added
// for warnings that are emitted in the redis client constructor
process . nextTick ( function ( ) {
if ( self . listeners ( 'warning' ) . length !== 0 ) {
self . emit ( 'warning' , msg ) ;
} else {
console . warn ( 'node_redis:' , msg ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
} ) ;
} ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
// Flush provided queues, erroring any items with a callback first
RedisClient . prototype . flush _and _error = function ( error _attributes , options ) {
options = options || { } ;
var aggregated _errors = [ ] ;
var queue _names = options . queues || [ 'command_queue' , 'offline_queue' ] ; // Flush the command_queue first to keep the order intakt
for ( var i = 0 ; i < queue _names . length ; i ++ ) {
// If the command was fired it might have been processed so far
if ( queue _names [ i ] === 'command_queue' ) {
error _attributes . message += ' It might have been processed.' ;
} else { // As the command_queue is flushed first, remove this for the offline queue
error _attributes . message = error _attributes . message . replace ( ' It might have been processed.' , '' ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
// Don't flush everything from the queue
for ( var command _obj = this [ queue _names [ i ] ] . shift ( ) ; command _obj ; command _obj = this [ queue _names [ i ] ] . shift ( ) ) {
var err = new errorClasses . AbortError ( error _attributes ) ;
if ( command _obj . error ) {
err . stack = err . stack + command _obj . error . stack . replace ( /^Error.*?\n/ , '\n' ) ;
}
err . command = command _obj . command . toUpperCase ( ) ;
if ( command _obj . args && command _obj . args . length ) {
err . args = command _obj . args ;
}
if ( options . error ) {
err . origin = options . error ;
}
if ( typeof command _obj . callback === 'function' ) {
command _obj . callback ( err ) ;
} else {
aggregated _errors . push ( err ) ;
}
}
}
// Currently this would be a breaking change, therefore it's only emitted in debug_mode
if ( exports . debug _mode && aggregated _errors . length ) {
var error ;
if ( aggregated _errors . length === 1 ) {
error = aggregated _errors [ 0 ] ;
} else {
error _attributes . message = error _attributes . message . replace ( 'It' , 'They' ) . replace ( /command/i , '$&s' ) ;
error = new errorClasses . AggregateError ( error _attributes ) ;
error . errors = aggregated _errors ;
}
this . emit ( 'error' , error ) ;
2012-01-02 18:22:06 -08:00
}
} ;
2016-11-29 18:29:50 -08:00
RedisClient . prototype . on _error = function ( err ) {
2012-01-02 18:22:06 -08:00
if ( this . closing ) {
return ;
}
2016-11-29 18:29:50 -08:00
err . message = 'Redis connection to ' + this . address + ' failed - ' + err . message ;
debug ( err . message ) ;
2012-01-02 18:22:06 -08:00
this . connected = false ;
this . ready = false ;
2016-11-29 18:29:50 -08:00
// Only emit the error if the retry_stategy option is not set
if ( ! this . options . retry _strategy ) {
this . emit ( 'error' , err ) ;
}
// 'error' events get turned into exceptions if they aren't listened for. If the user handled this error
2012-01-02 18:22:06 -08:00
// then we should try to reconnect.
2016-11-29 18:29:50 -08:00
this . connection _gone ( 'error' , err ) ;
2012-01-02 18:22:06 -08:00
} ;
RedisClient . prototype . on _connect = function ( ) {
2016-11-29 18:29:50 -08:00
debug ( 'Stream connected ' + this . address + ' id ' + this . connection _id ) ;
2012-01-02 18:22:06 -08:00
this . connected = true ;
this . ready = false ;
this . emitted _end = false ;
2016-11-29 18:29:50 -08:00
this . stream . setKeepAlive ( this . options . socket _keepalive ) ;
2012-01-02 18:22:06 -08:00
this . stream . setTimeout ( 0 ) ;
2016-11-29 18:29:50 -08:00
this . emit ( 'connect' ) ;
this . initialize _retry _vars ( ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
if ( this . options . no _ready _check ) {
this . on _ready ( ) ;
2012-01-02 18:22:06 -08:00
} else {
2016-11-29 18:29:50 -08:00
this . ready _check ( ) ;
2012-01-02 18:22:06 -08:00
}
} ;
2016-11-29 18:29:50 -08:00
RedisClient . prototype . on _ready = function ( ) {
2012-01-02 18:22:06 -08:00
var self = this ;
2016-11-29 18:29:50 -08:00
debug ( 'on_ready called ' + this . address + ' id ' + this . connection _id ) ;
this . ready = true ;
this . cork = function ( ) {
self . pipeline = true ;
if ( self . stream . cork ) {
self . stream . cork ( ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
} ;
this . uncork = function ( ) {
if ( self . fire _strings ) {
self . write _strings ( ) ;
} else {
self . write _buffers ( ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
self . pipeline = false ;
self . fire _strings = true ;
if ( self . stream . uncork ) {
// TODO: Consider using next tick here. See https://github.com/NodeRedis/node_redis/issues/1033
self . stream . uncork ( ) ;
}
} ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
// Restore modal commands from previous connection. The order of the commands is important
if ( this . selected _db !== undefined ) {
this . internal _send _command ( new Command ( 'select' , [ this . selected _db ] ) ) ;
}
if ( this . monitoring ) { // Monitor has to be fired before pub sub commands
this . internal _send _command ( new Command ( 'monitor' , [ ] ) ) ;
}
var callback _count = Object . keys ( this . subscription _set ) . length ;
if ( ! this . options . disable _resubscribing && callback _count ) {
// only emit 'ready' when all subscriptions were made again
// TODO: Remove the countdown for ready here. This is not coherent with all other modes and should therefore not be handled special
// We know we are ready as soon as all commands were fired
var callback = function ( ) {
callback _count -- ;
if ( callback _count === 0 ) {
self . emit ( 'ready' ) ;
}
} ;
debug ( 'Sending pub/sub on_ready commands' ) ;
for ( var key in this . subscription _set ) {
var command = key . slice ( 0 , key . indexOf ( '_' ) ) ;
var args = this . subscription _set [ key ] ;
this [ command ] ( [ args ] , callback ) ;
}
this . send _offline _queue ( ) ;
return ;
}
this . send _offline _queue ( ) ;
this . emit ( 'ready' ) ;
2012-01-02 18:22:06 -08:00
} ;
RedisClient . prototype . on _info _cmd = function ( err , res ) {
if ( err ) {
2016-11-29 18:29:50 -08:00
if ( err . message === "ERR unknown command 'info'" ) {
this . on _ready ( ) ;
return ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
err . message = 'Ready check failed: ' + err . message ;
this . emit ( 'error' , err ) ;
return ;
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
/* istanbul ignore if: some servers might not respond with any info data. This is just a safety check that is difficult to test */
if ( ! res ) {
debug ( 'The info command returned without any data.' ) ;
this . on _ready ( ) ;
return ;
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
if ( ! this . server _info . loading || this . server _info . loading === '0' ) {
// If the master_link_status exists but the link is not up, try again after 50 ms
if ( this . server _info . master _link _status && this . server _info . master _link _status !== 'up' ) {
this . server _info . loading _eta _seconds = 0.05 ;
} else {
// Eta loading should change
debug ( 'Redis server ready.' ) ;
this . on _ready ( ) ;
return ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
var retry _time = + this . server _info . loading _eta _seconds * 1000 ;
if ( retry _time > 1000 ) {
retry _time = 1000 ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
debug ( 'Redis server still loading, trying again in ' + retry _time ) ;
setTimeout ( function ( self ) {
self . ready _check ( ) ;
} , retry _time , this ) ;
2012-01-02 18:22:06 -08:00
} ;
RedisClient . prototype . ready _check = function ( ) {
var self = this ;
2016-11-29 18:29:50 -08:00
debug ( 'Checking server ready state...' ) ;
// Always fire this info command as first command even if other commands are already queued up
this . ready = true ;
2012-01-02 18:22:06 -08:00
this . info ( function ( err , res ) {
self . on _info _cmd ( err , res ) ;
} ) ;
2016-11-29 18:29:50 -08:00
this . ready = false ;
2012-01-02 18:22:06 -08:00
} ;
RedisClient . prototype . send _offline _queue = function ( ) {
2016-11-29 18:29:50 -08:00
for ( var command _obj = this . offline _queue . shift ( ) ; command _obj ; command _obj = this . offline _queue . shift ( ) ) {
debug ( 'Sending offline command: ' + command _obj . command ) ;
this . internal _send _command ( command _obj ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
this . drain ( ) ;
} ;
var retry _connection = function ( self , error ) {
debug ( 'Retrying connection...' ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
var reconnect _params = {
delay : self . retry _delay ,
attempt : self . attempts ,
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 ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
self . emit ( 'reconnecting' , reconnect _params ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
self . retry _totaltime += self . retry _delay ;
self . attempts += 1 ;
self . retry _delay = Math . round ( self . retry _delay * self . retry _backoff ) ;
self . create _stream ( ) ;
self . retry _timer = null ;
} ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
RedisClient . prototype . connection _gone = function ( why , error ) {
2012-01-02 18:22:06 -08:00
// If a retry is already in progress, just let that happen
if ( this . retry _timer ) {
return ;
}
2016-11-29 18:29:50 -08:00
error = error || null ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
debug ( 'Redis connection is gone from ' + why + ' event.' ) ;
2012-01-02 18:22:06 -08:00
this . connected = false ;
this . ready = false ;
2016-11-29 18:29:50 -08:00
// Deactivate cork to work with the offline queue
this . cork = noop ;
this . uncork = noop ;
this . pipeline = false ;
this . pub _sub _mode = 0 ;
2012-01-02 18:22:06 -08:00
// since we are collapsing end and close, users don't expect to be called twice
2016-11-29 18:29:50 -08:00
if ( ! this . emitted _end ) {
this . emit ( 'end' ) ;
2012-01-02 18:22:06 -08:00
this . emitted _end = true ;
}
// If this is a requested shutdown, then don't retry
if ( this . closing ) {
2016-11-29 18:29:50 -08:00
debug ( 'Connection ended by quit / end command, not retrying.' ) ;
this . flush _and _error ( {
message : 'Stream connection ended and command aborted.' ,
code : 'NR_CLOSED'
} , {
error : error
} ) ;
2012-01-02 18:22:06 -08:00
return ;
}
2016-11-29 18:29:50 -08:00
if ( typeof this . options . retry _strategy === 'function' ) {
var retry _params = {
attempt : this . attempts ,
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 ) {
error = this . retry _delay ;
}
this . flush _and _error ( {
message : 'Stream connection ended and command aborted.' ,
code : 'NR_CLOSED'
} , {
error : error
} ) ;
this . end ( false ) ;
return ;
}
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
if ( this . max _attempts !== 0 && this . attempts >= this . max _attempts || this . retry _totaltime >= this . connect _timeout ) {
var message = 'Redis connection in broken state: ' ;
if ( this . retry _totaltime >= this . connect _timeout ) {
message += 'connection timeout exceeded.' ;
} else {
message += 'maximum connection attempts exceeded.' ;
}
this . flush _and _error ( {
message : message ,
code : 'CONNECTION_BROKEN' ,
} , {
error : error
} ) ;
var err = new Error ( message ) ;
err . code = 'CONNECTION_BROKEN' ;
if ( error ) {
err . origin = error ;
}
this . emit ( 'error' , err ) ;
this . end ( false ) ;
2012-01-02 18:22:06 -08:00
return ;
}
2016-11-29 18:29:50 -08:00
// Retry commands after a reconnect instead of throwing an error. Use this with caution
if ( this . options . retry _unfulfilled _commands ) {
this . offline _queue . unshift . apply ( this . offline _queue , this . command _queue . toArray ( ) ) ;
this . command _queue . clear ( ) ;
} else if ( this . command _queue . length !== 0 ) {
this . flush _and _error ( {
message : 'Redis connection lost and command aborted.' ,
code : 'UNCERTAIN_STATE'
} , {
error : error ,
queues : [ 'command_queue' ]
} ) ;
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
if ( this . retry _max _delay !== null && this . retry _delay > this . retry _max _delay ) {
this . retry _delay = this . retry _max _delay ;
} else if ( this . retry _totaltime + this . retry _delay > this . connect _timeout ) {
// Do not exceed the maximum
this . retry _delay = this . connect _timeout - this . retry _totaltime ;
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
debug ( 'Retry connection in ' + this . retry _delay + ' ms' ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
this . retry _timer = setTimeout ( retry _connection , this . retry _delay , this , error ) ;
2012-01-02 18:22:06 -08:00
} ;
2016-11-29 18:29:50 -08:00
RedisClient . prototype . return _error = function ( err ) {
var command _obj = this . command _queue . shift ( ) ;
if ( command _obj . error ) {
err . stack = command _obj . error . stack . replace ( /^Error.*?\n/ , 'ReplyError: ' + err . message + '\n' ) ;
}
err . command = command _obj . command . toUpperCase ( ) ;
if ( command _obj . args && command _obj . args . length ) {
err . args = command _obj . args ;
}
// Count down pub sub mode if in entering modus
if ( this . pub _sub _mode > 1 ) {
this . pub _sub _mode -- ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
var match = err . message . match ( utils . err _code ) ;
// LUA script could return user errors that don't behave like all other errors!
if ( match ) {
err . code = match [ 1 ] ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
utils . callback _or _emit ( this , command _obj . callback , err ) ;
2012-01-02 18:22:06 -08:00
} ;
2016-11-29 18:29:50 -08:00
RedisClient . prototype . drain = function ( ) {
this . emit ( 'drain' ) ;
this . should _buffer = false ;
} ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
RedisClient . prototype . emit _idle = function ( ) {
if ( this . command _queue . length === 0 && this . pub _sub _mode === 0 ) {
this . emit ( 'idle' ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
} ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
function normal _reply ( self , reply ) {
var command _obj = self . command _queue . shift ( ) ;
if ( typeof command _obj . callback === 'function' ) {
if ( command _obj . command !== 'exec' ) {
reply = self . handle _reply ( reply , command _obj . command , command _obj . buffer _args ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
command _obj . callback ( null , reply ) ;
2012-01-02 18:22:06 -08:00
} else {
2016-11-29 18:29:50 -08:00
debug ( 'No callback for reply' ) ;
2012-01-02 18:22:06 -08:00
}
}
2016-11-29 18:29:50 -08:00
function subscribe _unsubscribe ( self , reply , type ) {
// Subscribe commands take an optional callback and also emit an event, but only the _last_ response is included in the callback
// The pub sub commands return each argument in a separate return value and have to be handled that way
var command _obj = self . command _queue . get ( 0 ) ;
var buffer = self . options . return _buffers || self . options . detect _buffers && command _obj . buffer _args ;
var channel = ( buffer || reply [ 1 ] === null ) ? reply [ 1 ] : reply [ 1 ] . toString ( ) ;
var count = + reply [ 2 ] ; // Return the channel counter as number no matter if `string_numbers` is activated or not
debug ( type , channel ) ;
// Emit first, then return the callback
if ( channel !== null ) { // Do not emit or "unsubscribe" something if there was no channel to unsubscribe from
self . emit ( type , channel , count ) ;
if ( type === 'subscribe' || type === 'psubscribe' ) {
self . subscription _set [ type + '_' + channel ] = channel ;
} else {
type = type === 'unsubscribe' ? 'subscribe' : 'psubscribe' ; // Make types consistent
delete self . subscription _set [ type + '_' + channel ] ;
}
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
if ( command _obj . args . length === 1 || self . sub _commands _left === 1 || command _obj . args . length === 0 && ( count === 0 || channel === null ) ) {
if ( count === 0 ) { // unsubscribed from all channels
var running _command ;
var i = 1 ;
self . pub _sub _mode = 0 ; // Deactivating pub sub mode
// This should be a rare case and therefore handling it this way should be good performance wise for the general case
while ( running _command = self . command _queue . get ( i ) ) {
if ( SUBSCRIBE _COMMANDS [ running _command . command ] ) {
self . pub _sub _mode = i ; // Entering pub sub mode again
break ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
i ++ ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
}
self . command _queue . shift ( ) ;
if ( typeof command _obj . callback === 'function' ) {
// TODO: The current return value is pretty useless.
// Evaluate to change this in v.3 to return all subscribed / unsubscribed channels in an array including the number of channels subscribed too
command _obj . callback ( null , channel ) ;
}
self . sub _commands _left = 0 ;
2012-01-02 18:22:06 -08:00
} else {
2016-11-29 18:29:50 -08:00
if ( self . sub _commands _left !== 0 ) {
self . sub _commands _left -- ;
} else {
self . sub _commands _left = command _obj . args . length ? command _obj . args . length - 1 : count ;
}
2012-01-02 18:22:06 -08:00
}
}
2016-11-29 18:29:50 -08:00
function return _pub _sub ( self , reply ) {
var type = reply [ 0 ] . toString ( ) ;
if ( type === 'message' ) { // channel, message
if ( ! self . options . return _buffers || self . message _buffers ) { // backwards compatible. Refactor this in v.3 to always return a string on the normal emitter
self . emit ( 'message' , reply [ 1 ] . toString ( ) , reply [ 2 ] . toString ( ) ) ;
self . emit ( 'message_buffer' , reply [ 1 ] , reply [ 2 ] ) ;
self . emit ( 'messageBuffer' , reply [ 1 ] , reply [ 2 ] ) ;
} else {
self . emit ( 'message' , reply [ 1 ] , reply [ 2 ] ) ;
}
} else if ( type === 'pmessage' ) { // pattern, channel, message
if ( ! self . options . return _buffers || self . message _buffers ) { // backwards compatible. Refactor this in v.3 to always return a string on the normal emitter
self . emit ( 'pmessage' , reply [ 1 ] . toString ( ) , reply [ 2 ] . toString ( ) , reply [ 3 ] . toString ( ) ) ;
self . emit ( 'pmessage_buffer' , reply [ 1 ] , reply [ 2 ] , reply [ 3 ] ) ;
self . emit ( 'pmessageBuffer' , reply [ 1 ] , reply [ 2 ] , reply [ 3 ] ) ;
2012-01-02 18:22:06 -08:00
} else {
2016-11-29 18:29:50 -08:00
self . emit ( 'pmessage' , reply [ 1 ] , reply [ 2 ] , reply [ 3 ] ) ;
2012-01-02 18:22:06 -08:00
}
} else {
2016-11-29 18:29:50 -08:00
subscribe _unsubscribe ( self , reply , type ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
RedisClient . prototype . return _reply = function ( reply ) {
if ( this . monitoring ) {
var replyStr ;
if ( this . buffers && Buffer . isBuffer ( reply ) ) {
replyStr = reply . toString ( ) ;
} else {
replyStr = reply ;
}
2019-04-13 16:01:51 -04:00
// If in monitor mode, all normal commands are still working and we only want to emit the streamlined commands
2016-11-29 18:29:50 -08:00
if ( typeof replyStr === 'string' && utils . monitor _regex . test ( replyStr ) ) {
var timestamp = replyStr . slice ( 0 , replyStr . indexOf ( ' ' ) ) ;
var args = replyStr . slice ( replyStr . indexOf ( '"' ) + 1 , - 1 ) . split ( '" "' ) . map ( function ( elem ) {
return elem . replace ( /\\"/g , '"' ) ;
} ) ;
this . emit ( 'monitor' , timestamp , args , replyStr ) ;
return ;
}
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
if ( this . pub _sub _mode === 0 ) {
normal _reply ( this , reply ) ;
} else if ( this . pub _sub _mode !== 1 ) {
this . pub _sub _mode -- ;
normal _reply ( this , reply ) ;
} else if ( ! ( reply instanceof Array ) || reply . length <= 2 ) {
// Only PING and QUIT are allowed in this context besides the pub sub commands
// Ping replies with ['pong', null|value] and quit with 'OK'
normal _reply ( this , reply ) ;
} else {
return _pub _sub ( this , reply ) ;
}
} ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
function handle _offline _command ( self , command _obj ) {
var command = command _obj . command ;
var err , msg ;
if ( self . closing || ! self . enable _offline _queue ) {
command = command . toUpperCase ( ) ;
if ( ! self . closing ) {
if ( self . stream . writable ) {
msg = 'The connection is not yet established and the offline queue is deactivated.' ;
} else {
msg = 'Stream not writeable.' ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
} else {
msg = 'The connection is already closed.' ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
err = new errorClasses . AbortError ( {
message : command + " can't be processed. " + msg ,
code : 'NR_CLOSED' ,
command : command
} ) ;
if ( command _obj . args . length ) {
err . args = command _obj . args ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
utils . reply _in _order ( self , command _obj . callback , err ) ;
} else {
debug ( 'Queueing ' + command + ' for next server connection.' ) ;
self . offline _queue . push ( command _obj ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
self . should _buffer = true ;
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
// Do not call internal_send_command directly, if you are not absolutly certain it handles everything properly
// e.g. monitor / info does not work with internal_send_command only
RedisClient . prototype . internal _send _command = function ( command _obj ) {
var arg , prefix _keys ;
var i = 0 ;
var command _str = '' ;
var args = command _obj . args ;
var command = command _obj . command ;
var len = args . length ;
var big _data = false ;
var args _copy = new Array ( len ) ;
if ( process . domain && command _obj . callback ) {
command _obj . callback = process . domain . bind ( command _obj . callback ) ;
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
if ( this . ready === false || this . stream . writable === false ) {
// Handle offline commands right away
handle _offline _command ( this , command _obj ) ;
return false ; // Indicate buffering
}
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
for ( i = 0 ; i < len ; i += 1 ) {
if ( typeof args [ i ] === 'string' ) {
// 30000 seemed to be a good value to switch to buffers after testing and checking the pros and cons
if ( args [ i ] . length > 30000 ) {
big _data = true ;
args _copy [ i ] = new Buffer ( args [ i ] , 'utf8' ) ;
} else {
args _copy [ i ] = args [ i ] ;
}
} else if ( typeof args [ i ] === 'object' ) { // Checking for object instead of Buffer.isBuffer helps us finding data types that we can't handle properly
if ( args [ i ] instanceof Date ) { // Accept dates as valid input
args _copy [ i ] = args [ i ] . toString ( ) ;
} else if ( args [ i ] === null ) {
this . warn (
'Deprecated: The ' + command . toUpperCase ( ) + ' command contains a "null" argument.\n' +
'This is converted to a "null" string now and will return an error from v.3.0 on.\n' +
'Please handle this in your code to make sure everything works as you intended it to.'
) ;
args _copy [ i ] = 'null' ; // Backwards compatible :/
} else if ( Buffer . isBuffer ( args [ i ] ) ) {
args _copy [ i ] = args [ i ] ;
command _obj . buffer _args = true ;
big _data = true ;
} else {
this . warn (
'Deprecated: The ' + command . toUpperCase ( ) + ' command contains a argument of type ' + args [ i ] . constructor . name + '.\n' +
'This is converted to "' + args [ i ] . toString ( ) + '" by using .toString() now and will return an error from v.3.0 on.\n' +
'Please handle this in your code to make sure everything works as you intended it to.'
) ;
args _copy [ i ] = args [ i ] . toString ( ) ; // Backwards compatible :/
}
} else if ( typeof args [ i ] === 'undefined' ) {
this . warn (
'Deprecated: The ' + command . toUpperCase ( ) + ' command contains a "undefined" argument.\n' +
'This is converted to a "undefined" string now and will return an error from v.3.0 on.\n' +
'Please handle this in your code to make sure everything works as you intended it to.'
) ;
args _copy [ i ] = 'undefined' ; // Backwards compatible :/
} else {
// Seems like numbers are converted fast using string concatenation
args _copy [ i ] = '' + args [ i ] ;
2012-01-02 18:22:06 -08:00
}
}
2016-11-29 18:29:50 -08:00
if ( this . options . prefix ) {
prefix _keys = commands . getKeyIndexes ( command , args _copy ) ;
for ( i = prefix _keys . pop ( ) ; i !== undefined ; i = prefix _keys . pop ( ) ) {
args _copy [ i ] = this . options . prefix + args _copy [ i ] ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
}
2019-04-13 16:01:51 -04:00
if ( this . options . rename _commands && this . options . rename _commands [ command ] ) {
2016-11-29 18:29:50 -08:00
command = this . options . rename _commands [ command ] ;
}
// Always use 'Multi bulk commands', but if passed any Buffer args, then do multiple writes, one for each arg.
// This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer.
command _str = '*' + ( len + 1 ) + '\r\n$' + command . length + '\r\n' + command + '\r\n' ;
if ( big _data === false ) { // Build up a string and send entire command in one write
for ( i = 0 ; i < len ; i += 1 ) {
arg = args _copy [ i ] ;
command _str += '$' + Buffer . byteLength ( arg ) + '\r\n' + arg + '\r\n' ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
debug ( 'Send ' + this . address + ' id ' + this . connection _id + ': ' + command _str ) ;
this . write ( command _str ) ;
2012-01-02 18:22:06 -08:00
} else {
2016-11-29 18:29:50 -08:00
debug ( 'Send command (' + command _str + ') has Buffer arguments' ) ;
this . fire _strings = false ;
this . write ( command _str ) ;
for ( i = 0 ; i < len ; i += 1 ) {
arg = args _copy [ i ] ;
if ( typeof arg === 'string' ) {
this . write ( '$' + Buffer . byteLength ( arg ) + '\r\n' + arg + '\r\n' ) ;
} else { // buffer
this . write ( '$' + arg . length + '\r\n' ) ;
this . write ( arg ) ;
this . write ( '\r\n' ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
debug ( 'send_command: buffer send ' + arg . length + ' bytes' ) ;
2012-01-02 18:22:06 -08:00
}
}
2016-11-29 18:29:50 -08:00
if ( command _obj . call _on _write ) {
command _obj . call _on _write ( ) ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
// Handle `CLIENT REPLY ON|OFF|SKIP`
// This has to be checked after call_on_write
/* istanbul ignore else: TODO: Remove this as soon as we test Redis 3.2 on travis */
if ( this . reply === 'ON' ) {
this . command _queue . push ( command _obj ) ;
} else {
// Do not expect a reply
// Does this work in combination with the pub sub mode?
if ( command _obj . callback ) {
utils . reply _in _order ( this , command _obj . callback , null , undefined , this . command _queue ) ;
}
if ( this . reply === 'SKIP' ) {
this . reply = 'SKIP_ONE_MORE' ;
} else if ( this . reply === 'SKIP_ONE_MORE' ) {
this . reply = 'ON' ;
}
2012-01-02 18:22:06 -08:00
}
return ! this . should _buffer ;
} ;
2016-11-29 18:29:50 -08:00
RedisClient . prototype . write _strings = function ( ) {
var str = '' ;
for ( var command = this . pipeline _queue . shift ( ) ; command ; command = this . pipeline _queue . shift ( ) ) {
// Write to stream if the string is bigger than 4mb. The biggest string may be Math.pow(2, 28) - 15 chars long
if ( str . length + command . length > 4 * 1024 * 1024 ) {
this . should _buffer = ! this . stream . write ( str ) ;
str = '' ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
str += command ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
if ( str !== '' ) {
this . should _buffer = ! this . stream . write ( str ) ;
2012-01-02 18:22:06 -08:00
}
} ;
2016-11-29 18:29:50 -08:00
RedisClient . prototype . write _buffers = function ( ) {
for ( var command = this . pipeline _queue . shift ( ) ; command ; command = this . pipeline _queue . shift ( ) ) {
this . should _buffer = ! this . stream . write ( command ) ;
2012-01-02 18:22:06 -08:00
}
} ;
2016-11-29 18:29:50 -08:00
RedisClient . prototype . write = function ( data ) {
if ( this . pipeline === false ) {
this . should _buffer = ! this . stream . write ( data ) ;
return ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
this . pipeline _queue . push ( data ) ;
} ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
Object . defineProperty ( exports , 'debugMode' , {
get : function ( ) {
return this . debug _mode ;
} ,
set : function ( val ) {
this . debug _mode = val ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
} ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
// 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 ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
} ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
Object . defineProperty ( RedisClient . prototype , 'offline_queue_length' , {
get : function ( ) {
return this . offline _queue . length ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
} ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
// Add support for camelCase by adding read only properties to the client
// All known exposed snake_case variables are added here
Object . defineProperty ( RedisClient . prototype , 'retryDelay' , {
get : function ( ) {
return this . retry _delay ;
}
} ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
Object . defineProperty ( RedisClient . prototype , 'retryBackoff' , {
get : function ( ) {
return this . retry _backoff ;
}
} ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
Object . defineProperty ( RedisClient . prototype , 'commandQueueLength' , {
get : function ( ) {
return this . command _queue . length ;
}
} ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
Object . defineProperty ( RedisClient . prototype , 'offlineQueueLength' , {
get : function ( ) {
return this . offline _queue . length ;
}
} ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
Object . defineProperty ( RedisClient . prototype , 'shouldBuffer' , {
get : function ( ) {
return this . should _buffer ;
}
} ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
Object . defineProperty ( RedisClient . prototype , 'connectionId' , {
get : function ( ) {
return this . connection _id ;
}
} ) ;
2012-01-02 18:22:06 -08:00
2016-11-29 18:29:50 -08:00
Object . defineProperty ( RedisClient . prototype , 'serverInfo' , {
get : function ( ) {
return this . server _info ;
2012-01-02 18:22:06 -08:00
}
2016-11-29 18:29:50 -08:00
} ) ;
exports . createClient = function ( ) {
return new RedisClient ( unifyOptions . apply ( null , arguments ) ) ;
2012-01-02 18:22:06 -08:00
} ;
2016-11-29 18:29:50 -08:00
exports . RedisClient = RedisClient ;
exports . print = utils . print ;
exports . Multi = require ( './lib/multi' ) ;
exports . AbortError = errorClasses . AbortError ;
2019-04-13 16:01:51 -04:00
exports . RedisError = Parser . RedisError ;
exports . ParserError = Parser . ParserError ;
2016-11-29 18:29:50 -08:00
exports . ReplyError = Parser . ReplyError ;
exports . AggregateError = errorClasses . AggregateError ;
// Add all redis commands / node_redis api to the client
require ( './lib/individualCommands' ) ;
require ( './lib/extendedApi' ) ;
2019-04-13 16:01:51 -04:00
//enables adding new commands (for modules and new commands)
exports . addCommand = exports . add _command = require ( './lib/commands' ) ;