redis 3.1.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/package.json +1 -1
  2. package/.deepsource.toml +0 -9
  3. package/heroku/index.js +0 -14
  4. package/heroku/node_modules/.package-lock.json +0 -57
  5. package/heroku/node_modules/denque/CHANGELOG.md +0 -4
  6. package/heroku/node_modules/denque/LICENSE +0 -13
  7. package/heroku/node_modules/denque/README.md +0 -362
  8. package/heroku/node_modules/denque/index.d.ts +0 -31
  9. package/heroku/node_modules/denque/index.js +0 -443
  10. package/heroku/node_modules/denque/package.json +0 -55
  11. package/heroku/node_modules/redis/.deepsource.toml +0 -9
  12. package/heroku/node_modules/redis/CHANGELOG.md +0 -880
  13. package/heroku/node_modules/redis/LICENSE +0 -24
  14. package/heroku/node_modules/redis/README.md +0 -1009
  15. package/heroku/node_modules/redis/a.js +0 -12
  16. package/heroku/node_modules/redis/index.js +0 -1039
  17. package/heroku/node_modules/redis/lib/command.js +0 -16
  18. package/heroku/node_modules/redis/lib/commands.js +0 -105
  19. package/heroku/node_modules/redis/lib/createClient.js +0 -88
  20. package/heroku/node_modules/redis/lib/customErrors.js +0 -58
  21. package/heroku/node_modules/redis/lib/debug.js +0 -13
  22. package/heroku/node_modules/redis/lib/extendedApi.js +0 -113
  23. package/heroku/node_modules/redis/lib/individualCommands.js +0 -629
  24. package/heroku/node_modules/redis/lib/multi.js +0 -187
  25. package/heroku/node_modules/redis/lib/utils.js +0 -134
  26. package/heroku/node_modules/redis/npm +0 -0
  27. package/heroku/node_modules/redis/package.json +0 -77
  28. package/heroku/node_modules/redis-commands/LICENSE +0 -22
  29. package/heroku/node_modules/redis-commands/README.md +0 -51
  30. package/heroku/node_modules/redis-commands/changelog.md +0 -83
  31. package/heroku/node_modules/redis-commands/commands.json +0 -2334
  32. package/heroku/node_modules/redis-commands/index.js +0 -168
  33. package/heroku/node_modules/redis-commands/package.json +0 -41
  34. package/heroku/node_modules/redis-commands/tools/build.js +0 -62
  35. package/heroku/node_modules/redis-errors/LICENSE +0 -22
  36. package/heroku/node_modules/redis-errors/README.md +0 -116
  37. package/heroku/node_modules/redis-errors/index.js +0 -7
  38. package/heroku/node_modules/redis-errors/lib/modern.js +0 -59
  39. package/heroku/node_modules/redis-errors/lib/old.js +0 -119
  40. package/heroku/node_modules/redis-errors/package.json +0 -41
  41. package/heroku/node_modules/redis-parser/LICENSE +0 -22
  42. package/heroku/node_modules/redis-parser/README.md +0 -166
  43. package/heroku/node_modules/redis-parser/changelog.md +0 -156
  44. package/heroku/node_modules/redis-parser/index.js +0 -3
  45. package/heroku/node_modules/redis-parser/lib/parser.js +0 -552
  46. package/heroku/node_modules/redis-parser/package.json +0 -53
  47. package/heroku/package.json +0 -9
@@ -1,1039 +0,0 @@
1
- 'use strict';
2
-
3
- var net = require('net');
4
- var tls = require('tls');
5
- var util = require('util');
6
- var utils = require('./lib/utils');
7
- var Command = require('./lib/command');
8
- var Queue = require('denque');
9
- var errorClasses = require('./lib/customErrors');
10
- var EventEmitter = require('events');
11
- var Parser = require('redis-parser');
12
- var RedisErrors = require('redis-errors');
13
- var commands = require('redis-commands');
14
- var debug = require('./lib/debug');
15
- var unifyOptions = require('./lib/createClient');
16
- var SUBSCRIBE_COMMANDS = {
17
- subscribe: true,
18
- unsubscribe: true,
19
- psubscribe: true,
20
- punsubscribe: true
21
- };
22
-
23
- function noop () {}
24
-
25
- function handle_detect_buffers_reply (reply, command, buffer_args) {
26
- if (buffer_args === false || this.message_buffers) {
27
- // If detect_buffers option was specified, then the reply from the parser will be a buffer.
28
- // If this command did not use Buffer arguments, then convert the reply to Strings here.
29
- reply = utils.reply_to_strings(reply);
30
- }
31
-
32
- if (command === 'hgetall') {
33
- reply = utils.reply_to_object(reply);
34
- }
35
- return reply;
36
- }
37
-
38
- exports.debug_mode = /\bredis\b/i.test(process.env.NODE_DEBUG);
39
-
40
- // Attention: The second parameter might be removed at will and is not officially supported.
41
- // Do not rely on this
42
- function RedisClient (options, stream) {
43
- // Copy the options so they are not mutated
44
- options = utils.clone(options);
45
- EventEmitter.call(this);
46
- var cnx_options = {};
47
- var self = this;
48
- /* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
49
- for (var tls_option in options.tls) {
50
- cnx_options[tls_option] = options.tls[tls_option];
51
- // Copy the tls options into the general options to make sure the address is set right
52
- if (tls_option === 'port' || tls_option === 'host' || tls_option === 'path' || tls_option === 'family') {
53
- options[tls_option] = options.tls[tls_option];
54
- }
55
- }
56
- if (stream) {
57
- // The stream from the outside is used so no connection from this side is triggered but from the server this client should talk to
58
- // Reconnect etc won't work with this. This requires monkey patching to work, so it is not officially supported
59
- options.stream = stream;
60
- this.address = '"Private stream"';
61
- } else if (options.path) {
62
- cnx_options.path = options.path;
63
- this.address = options.path;
64
- } else {
65
- cnx_options.port = +options.port || 6379;
66
- cnx_options.host = options.host || '127.0.0.1';
67
- cnx_options.family = (!options.family && net.isIP(cnx_options.host)) || (options.family === 'IPv6' ? 6 : 4);
68
- this.address = cnx_options.host + ':' + cnx_options.port;
69
- }
70
-
71
- this.connection_options = cnx_options;
72
- this.connection_id = RedisClient.connection_id++;
73
- this.connected = false;
74
- this.ready = false;
75
- if (options.socket_keepalive === undefined) {
76
- options.socket_keepalive = true;
77
- }
78
- if (options.socket_initial_delay === undefined) {
79
- options.socket_initial_delay = 0;
80
- // set default to 0, which is aligned to https://nodejs.org/api/net.html#net_socket_setkeepalive_enable_initialdelay
81
- }
82
- for (var command in options.rename_commands) {
83
- options.rename_commands[command.toLowerCase()] = options.rename_commands[command];
84
- }
85
- options.return_buffers = !!options.return_buffers;
86
- options.detect_buffers = !!options.detect_buffers;
87
- // Override the detect_buffers setting if return_buffers is active and print a warning
88
- if (options.return_buffers && options.detect_buffers) {
89
- self.warn('WARNING: You activated return_buffers and detect_buffers at the same time. The return value is always going to be a buffer.');
90
- options.detect_buffers = false;
91
- }
92
- if (options.detect_buffers) {
93
- // We only need to look at the arguments if we do not know what we have to return
94
- this.handle_reply = handle_detect_buffers_reply;
95
- }
96
- this.should_buffer = false;
97
- this.command_queue = new Queue(); // Holds sent commands to de-pipeline them
98
- this.offline_queue = new Queue(); // Holds commands issued but not able to be sent
99
- this.pipeline_queue = new Queue(); // Holds all pipelined commands
100
- // ATTENTION: connect_timeout should change in v.3.0 so it does not count towards ending reconnection attempts after x seconds
101
- // This should be done by the retry_strategy. Instead it should only be the timeout for connecting to redis
102
- this.connect_timeout = +options.connect_timeout || 3600000; // 60 * 60 * 1000 ms
103
- this.enable_offline_queue = options.enable_offline_queue === false ? false : true;
104
- this.initialize_retry_vars();
105
- this.pub_sub_mode = 0;
106
- this.subscription_set = {};
107
- this.monitoring = false;
108
- this.message_buffers = false;
109
- this.closing = false;
110
- this.server_info = {};
111
- this.auth_pass = options.auth_pass || options.password;
112
- this.auth_user = options.auth_user || options.user;
113
- this.selected_db = options.db; // Save the selected db here, used when reconnecting
114
- this.fire_strings = true; // Determine if strings or buffers should be written to the stream
115
- this.pipeline = false;
116
- this.sub_commands_left = 0;
117
- this.times_connected = 0;
118
- this.buffers = options.return_buffers || options.detect_buffers;
119
- this.options = options;
120
- this.reply = 'ON'; // Returning replies is the default
121
- this.create_stream();
122
- // The listeners will not be attached right away, so let's print the deprecation message while the listener is attached
123
- this.on('newListener', function (event) {
124
- if ((event === 'message_buffer' || event === 'pmessage_buffer' || event === 'messageBuffer' || event === 'pmessageBuffer') && !this.buffers && !this.message_buffers) {
125
- this.reply_parser.optionReturnBuffers = true;
126
- this.message_buffers = true;
127
- this.handle_reply = handle_detect_buffers_reply;
128
- }
129
- });
130
- }
131
- util.inherits(RedisClient, EventEmitter);
132
-
133
- RedisClient.connection_id = 0;
134
-
135
- function create_parser (self) {
136
- return new Parser({
137
- returnReply: function (data) {
138
- self.return_reply(data);
139
- },
140
- returnError: function (err) {
141
- // Return a ReplyError to indicate Redis returned an error
142
- self.return_error(err);
143
- },
144
- returnFatalError: function (err) {
145
- // Error out all fired commands. Otherwise they might rely on faulty data. We have to reconnect to get in a working state again
146
- // Note: the execution order is important. First flush and emit, then create the stream
147
- err.message += '. Please report this.';
148
- self.ready = false;
149
- self.flush_and_error({
150
- message: 'Fatal error encountered. Command aborted.',
151
- code: 'NR_FATAL'
152
- }, {
153
- error: err,
154
- queues: ['command_queue']
155
- });
156
- self.emit('error', err);
157
- self.create_stream();
158
- },
159
- returnBuffers: self.buffers || self.message_buffers,
160
- stringNumbers: self.options.string_numbers || false
161
- });
162
- }
163
-
164
- /******************************************************************************
165
-
166
- All functions in here are internal besides the RedisClient constructor
167
- and the exported functions. Don't rely on them as they will be private
168
- functions in node_redis v.3
169
-
170
- ******************************************************************************/
171
-
172
- // Attention: the function name "create_stream" should not be changed, as other libraries need this to mock the stream (e.g. fakeredis)
173
- RedisClient.prototype.create_stream = function () {
174
- var self = this;
175
-
176
- // Init parser
177
- this.reply_parser = create_parser(this);
178
-
179
- if (this.options.stream) {
180
- // Only add the listeners once in case of a reconnect try (that won't work)
181
- if (this.stream) {
182
- return;
183
- }
184
- this.stream = this.options.stream;
185
- } else {
186
- // On a reconnect destroy the former stream and retry
187
- if (this.stream) {
188
- this.stream.removeAllListeners();
189
- this.stream.destroy();
190
- }
191
-
192
- /* istanbul ignore if: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
193
- if (this.options.tls) {
194
- this.stream = tls.connect(this.connection_options);
195
- } else {
196
- this.stream = net.createConnection(this.connection_options);
197
- }
198
- }
199
-
200
- if (this.options.connect_timeout) {
201
- this.stream.setTimeout(this.connect_timeout, function () {
202
- // Note: This is only tested if a internet connection is established
203
- self.retry_totaltime = self.connect_timeout;
204
- self.connection_gone('timeout');
205
- });
206
- }
207
-
208
- /* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
209
- var connect_event = this.options.tls ? 'secureConnect' : 'connect';
210
- this.stream.once(connect_event, function () {
211
- this.removeAllListeners('timeout');
212
- self.times_connected++;
213
- self.on_connect();
214
- });
215
-
216
- this.stream.on('data', function (buffer_from_socket) {
217
- // The buffer_from_socket.toString() has a significant impact on big chunks and therefore this should only be used if necessary
218
- debug('Net read ' + self.address + ' id ' + self.connection_id); // + ': ' + buffer_from_socket.toString());
219
- self.reply_parser.execute(buffer_from_socket);
220
- });
221
-
222
- this.stream.on('error', function (err) {
223
- self.on_error(err);
224
- });
225
-
226
- this.stream.once('close', function (hadError) {
227
- self.connection_gone('close');
228
- });
229
-
230
- this.stream.once('end', function () {
231
- self.connection_gone('end');
232
- });
233
-
234
- this.stream.on('drain', function () {
235
- self.drain();
236
- });
237
-
238
- this.stream.setNoDelay();
239
-
240
- // Fire the command before redis is connected to be sure it's the first fired command
241
- if (this.auth_pass !== undefined) {
242
- this.ready = true;
243
- // Fail silently as we might not be able to connect
244
- this.auth(this.auth_pass, this.auth_user, function (err) {
245
- if (err && err.code !== 'UNCERTAIN_STATE') {
246
- self.emit('error', err);
247
- }
248
- });
249
- this.ready = false;
250
- }
251
- };
252
-
253
- RedisClient.prototype.handle_reply = function (reply, command) {
254
- if (command === 'hgetall') {
255
- reply = utils.reply_to_object(reply);
256
- }
257
- return reply;
258
- };
259
-
260
- RedisClient.prototype.cork = noop;
261
- RedisClient.prototype.uncork = noop;
262
-
263
- RedisClient.prototype.initialize_retry_vars = function () {
264
- this.retry_timer = null;
265
- this.retry_totaltime = 0;
266
- this.retry_delay = 200;
267
- this.retry_backoff = 1.7;
268
- this.attempts = 1;
269
- };
270
-
271
- RedisClient.prototype.warn = function (msg) {
272
- var self = this;
273
- // Warn on the next tick. Otherwise no event listener can be added
274
- // for warnings that are emitted in the redis client constructor
275
- process.nextTick(function () {
276
- if (self.listeners('warning').length !== 0) {
277
- self.emit('warning', msg);
278
- } else {
279
- console.warn('node_redis:', msg);
280
- }
281
- });
282
- };
283
-
284
- // Flush provided queues, erroring any items with a callback first
285
- RedisClient.prototype.flush_and_error = function (error_attributes, options) {
286
- options = options || {};
287
- var aggregated_errors = [];
288
- var queue_names = options.queues || ['command_queue', 'offline_queue']; // Flush the command_queue first to keep the order intakt
289
- for (var i = 0; i < queue_names.length; i++) {
290
- // If the command was fired it might have been processed so far
291
- if (queue_names[i] === 'command_queue') {
292
- error_attributes.message += ' It might have been processed.';
293
- } else { // As the command_queue is flushed first, remove this for the offline queue
294
- error_attributes.message = error_attributes.message.replace(' It might have been processed.', '');
295
- }
296
- // Don't flush everything from the queue
297
- for (var command_obj = this[queue_names[i]].shift(); command_obj; command_obj = this[queue_names[i]].shift()) {
298
- var err = new errorClasses.AbortError(error_attributes);
299
- if (command_obj.error) {
300
- err.stack = err.stack + command_obj.error.stack.replace(/^Error.*?\n/, '\n');
301
- }
302
- err.command = command_obj.command.toUpperCase();
303
- if (command_obj.args && command_obj.args.length) {
304
- err.args = command_obj.args;
305
- }
306
- if (options.error) {
307
- err.origin = options.error;
308
- }
309
- if (typeof command_obj.callback === 'function') {
310
- command_obj.callback(err);
311
- } else {
312
- aggregated_errors.push(err);
313
- }
314
- }
315
- }
316
- // Currently this would be a breaking change, therefore it's only emitted in debug_mode
317
- if (exports.debug_mode && aggregated_errors.length) {
318
- var error;
319
- if (aggregated_errors.length === 1) {
320
- error = aggregated_errors[0];
321
- } else {
322
- error_attributes.message = error_attributes.message.replace('It', 'They').replace(/command/i, '$&s');
323
- error = new errorClasses.AggregateError(error_attributes);
324
- error.errors = aggregated_errors;
325
- }
326
- this.emit('error', error);
327
- }
328
- };
329
-
330
- RedisClient.prototype.on_error = function (err) {
331
- if (this.closing) {
332
- return;
333
- }
334
-
335
- err.message = 'Redis connection to ' + this.address + ' failed - ' + err.message;
336
- debug(err.message);
337
- this.connected = false;
338
- this.ready = false;
339
-
340
- // Only emit the error if the retry_strategy option is not set
341
- if (!this.options.retry_strategy) {
342
- this.emit('error', err);
343
- }
344
- // 'error' events get turned into exceptions if they aren't listened for. If the user handled this error
345
- // then we should try to reconnect.
346
- this.connection_gone('error', err);
347
- };
348
-
349
- RedisClient.prototype.on_connect = function () {
350
- debug('Stream connected ' + this.address + ' id ' + this.connection_id);
351
-
352
- this.connected = true;
353
- this.ready = false;
354
- this.emitted_end = false;
355
- this.stream.setKeepAlive(this.options.socket_keepalive, this.options.socket_initial_delay);
356
- this.stream.setTimeout(0);
357
-
358
- this.emit('connect');
359
- this.initialize_retry_vars();
360
-
361
- if (this.options.no_ready_check) {
362
- this.on_ready();
363
- } else {
364
- this.ready_check();
365
- }
366
- };
367
-
368
- RedisClient.prototype.on_ready = function () {
369
- var self = this;
370
-
371
- debug('on_ready called ' + this.address + ' id ' + this.connection_id);
372
- this.ready = true;
373
-
374
- this.cork = function () {
375
- self.pipeline = true;
376
- if (self.stream.cork) {
377
- self.stream.cork();
378
- }
379
- };
380
- this.uncork = function () {
381
- if (self.fire_strings) {
382
- self.write_strings();
383
- } else {
384
- self.write_buffers();
385
- }
386
- self.pipeline = false;
387
- self.fire_strings = true;
388
- if (self.stream.uncork) {
389
- // TODO: Consider using next tick here. See https://github.com/NodeRedis/node_redis/issues/1033
390
- self.stream.uncork();
391
- }
392
- };
393
-
394
- // Restore modal commands from previous connection. The order of the commands is important
395
- if (this.selected_db !== undefined) {
396
- this.internal_send_command(new Command('select', [this.selected_db]));
397
- }
398
- if (this.monitoring) { // Monitor has to be fired before pub sub commands
399
- this.internal_send_command(new Command('monitor', []));
400
- }
401
- var callback_count = Object.keys(this.subscription_set).length;
402
- if (!this.options.disable_resubscribing && callback_count) {
403
- // only emit 'ready' when all subscriptions were made again
404
- // TODO: Remove the countdown for ready here. This is not coherent with all other modes and should therefore not be handled special
405
- // We know we are ready as soon as all commands were fired
406
- var callback = function () {
407
- callback_count--;
408
- if (callback_count === 0) {
409
- self.emit('ready');
410
- }
411
- };
412
- debug('Sending pub/sub on_ready commands');
413
- for (var key in this.subscription_set) {
414
- var command = key.slice(0, key.indexOf('_'));
415
- var args = this.subscription_set[key];
416
- this[command]([args], callback);
417
- }
418
- this.send_offline_queue();
419
- return;
420
- }
421
- this.send_offline_queue();
422
- this.emit('ready');
423
- };
424
-
425
- RedisClient.prototype.on_info_cmd = function (err, res) {
426
- if (err) {
427
- if (err.message === "ERR unknown command 'info'") {
428
- this.on_ready();
429
- return;
430
- }
431
- err.message = 'Ready check failed: ' + err.message;
432
- this.emit('error', err);
433
- return;
434
- }
435
-
436
- /* istanbul ignore if: some servers might not respond with any info data. This is just a safety check that is difficult to test */
437
- if (!res) {
438
- debug('The info command returned without any data.');
439
- this.on_ready();
440
- return;
441
- }
442
-
443
- if (!this.server_info.loading || this.server_info.loading === '0') {
444
- // If the master_link_status exists but the link is not up, try again after 50 ms
445
- if (this.server_info.master_link_status && this.server_info.master_link_status !== 'up') {
446
- this.server_info.loading_eta_seconds = 0.05;
447
- } else {
448
- // Eta loading should change
449
- debug('Redis server ready.');
450
- this.on_ready();
451
- return;
452
- }
453
- }
454
-
455
- var retry_time = +this.server_info.loading_eta_seconds * 1000;
456
- if (retry_time > 1000) {
457
- retry_time = 1000;
458
- }
459
- debug('Redis server still loading, trying again in ' + retry_time);
460
- setTimeout(function (self) {
461
- self.ready_check();
462
- }, retry_time, this);
463
- };
464
-
465
- RedisClient.prototype.ready_check = function () {
466
- var self = this;
467
- debug('Checking server ready state...');
468
- // Always fire this info command as first command even if other commands are already queued up
469
- this.ready = true;
470
- this.info(function (err, res) {
471
- self.on_info_cmd(err, res);
472
- });
473
- this.ready = false;
474
- };
475
-
476
- RedisClient.prototype.send_offline_queue = function () {
477
- for (var command_obj = this.offline_queue.shift(); command_obj; command_obj = this.offline_queue.shift()) {
478
- debug('Sending offline command: ' + command_obj.command);
479
- this.internal_send_command(command_obj);
480
- }
481
- this.drain();
482
- };
483
-
484
- var retry_connection = function (self, error) {
485
- debug('Retrying connection...');
486
-
487
- var reconnect_params = {
488
- delay: self.retry_delay,
489
- attempt: self.attempts,
490
- error: error
491
- };
492
- if (self.options.camel_case) {
493
- reconnect_params.totalRetryTime = self.retry_totaltime;
494
- reconnect_params.timesConnected = self.times_connected;
495
- } else {
496
- reconnect_params.total_retry_time = self.retry_totaltime;
497
- reconnect_params.times_connected = self.times_connected;
498
- }
499
- self.emit('reconnecting', reconnect_params);
500
-
501
- self.retry_totaltime += self.retry_delay;
502
- self.attempts += 1;
503
- self.retry_delay = Math.round(self.retry_delay * self.retry_backoff);
504
- self.create_stream();
505
- self.retry_timer = null;
506
- };
507
-
508
- RedisClient.prototype.connection_gone = function (why, error) {
509
- // If a retry is already in progress, just let that happen
510
- if (this.retry_timer) {
511
- return;
512
- }
513
- error = error || null;
514
-
515
- debug('Redis connection is gone from ' + why + ' event.');
516
- this.connected = false;
517
- this.ready = false;
518
- // Deactivate cork to work with the offline queue
519
- this.cork = noop;
520
- this.uncork = noop;
521
- this.pipeline = false;
522
- this.pub_sub_mode = 0;
523
-
524
- // since we are collapsing end and close, users don't expect to be called twice
525
- if (!this.emitted_end) {
526
- this.emit('end');
527
- this.emitted_end = true;
528
- }
529
-
530
- // If this is a requested shutdown, then don't retry
531
- if (this.closing) {
532
- debug('Connection ended by quit / end command, not retrying.');
533
- this.flush_and_error({
534
- message: 'Stream connection ended and command aborted.',
535
- code: 'NR_CLOSED'
536
- }, {
537
- error: error
538
- });
539
- return;
540
- }
541
-
542
- if (typeof this.options.retry_strategy === 'function') {
543
- var retry_params = {
544
- attempt: this.attempts,
545
- error: error
546
- };
547
- if (this.options.camel_case) {
548
- retry_params.totalRetryTime = this.retry_totaltime;
549
- retry_params.timesConnected = this.times_connected;
550
- } else {
551
- retry_params.total_retry_time = this.retry_totaltime;
552
- retry_params.times_connected = this.times_connected;
553
- }
554
- this.retry_delay = this.options.retry_strategy(retry_params);
555
- if (typeof this.retry_delay !== 'number') {
556
- // Pass individual error through
557
- if (this.retry_delay instanceof Error) {
558
- error = this.retry_delay;
559
- }
560
-
561
- var errorMessage = 'Redis connection in broken state: retry aborted.';
562
-
563
- this.flush_and_error({
564
- message: errorMessage,
565
- code: 'CONNECTION_BROKEN',
566
- }, {
567
- error: error
568
- });
569
- var retryError = new Error(errorMessage);
570
- retryError.code = 'CONNECTION_BROKEN';
571
- if (error) {
572
- retryError.origin = error;
573
- }
574
- this.end(false);
575
- this.emit('error', retryError);
576
- return;
577
- }
578
- }
579
-
580
- if (this.retry_totaltime >= this.connect_timeout) {
581
- var message = 'Redis connection in broken state: connection timeout exceeded.';
582
- this.flush_and_error({
583
- message: message,
584
- code: 'CONNECTION_BROKEN',
585
- }, {
586
- error: error
587
- });
588
- var err = new Error(message);
589
- err.code = 'CONNECTION_BROKEN';
590
- if (error) {
591
- err.origin = error;
592
- }
593
- this.end(false);
594
- this.emit('error', err);
595
- return;
596
- }
597
-
598
- // Retry commands after a reconnect instead of throwing an error. Use this with caution
599
- if (this.options.retry_unfulfilled_commands) {
600
- this.offline_queue.unshift.apply(this.offline_queue, this.command_queue.toArray());
601
- this.command_queue.clear();
602
- } else if (this.command_queue.length !== 0) {
603
- this.flush_and_error({
604
- message: 'Redis connection lost and command aborted.',
605
- code: 'UNCERTAIN_STATE'
606
- }, {
607
- error: error,
608
- queues: ['command_queue']
609
- });
610
- }
611
-
612
- if (this.retry_totaltime + this.retry_delay > this.connect_timeout) {
613
- // Do not exceed the maximum
614
- this.retry_delay = this.connect_timeout - this.retry_totaltime;
615
- }
616
-
617
- debug('Retry connection in ' + this.retry_delay + ' ms');
618
- this.retry_timer = setTimeout(retry_connection, this.retry_delay, this, error);
619
- };
620
-
621
- RedisClient.prototype.return_error = function (err) {
622
- var command_obj = this.command_queue.shift();
623
- if (command_obj.error) {
624
- err.stack = command_obj.error.stack.replace(/^Error.*?\n/, 'ReplyError: ' + err.message + '\n');
625
- }
626
- err.command = command_obj.command.toUpperCase();
627
- if (command_obj.args && command_obj.args.length) {
628
- err.args = command_obj.args;
629
- }
630
-
631
- // Count down pub sub mode if in entering modus
632
- if (this.pub_sub_mode > 1) {
633
- this.pub_sub_mode--;
634
- }
635
-
636
- var match = err.message.match(utils.err_code);
637
- // LUA script could return user errors that don't behave like all other errors!
638
- if (match) {
639
- err.code = match[1];
640
- }
641
-
642
- utils.callback_or_emit(this, command_obj.callback, err);
643
- };
644
-
645
- RedisClient.prototype.drain = function () {
646
- this.should_buffer = false;
647
- };
648
-
649
- function normal_reply (self, reply) {
650
- var command_obj = self.command_queue.shift();
651
- if (typeof command_obj.callback === 'function') {
652
- if (command_obj.command !== 'exec') {
653
- reply = self.handle_reply(reply, command_obj.command, command_obj.buffer_args);
654
- }
655
- command_obj.callback(null, reply);
656
- } else {
657
- debug('No callback for reply');
658
- }
659
- }
660
-
661
- function subscribe_unsubscribe (self, reply, type) {
662
- // Subscribe commands take an optional callback and also emit an event, but only the _last_ response is included in the callback
663
- // The pub sub commands return each argument in a separate return value and have to be handled that way
664
- var command_obj = self.command_queue.get(0);
665
- var buffer = self.options.return_buffers || self.options.detect_buffers && command_obj.buffer_args;
666
- var channel = (buffer || reply[1] === null) ? reply[1] : reply[1].toString();
667
- var count = +reply[2]; // Return the channel counter as number no matter if `string_numbers` is activated or not
668
- debug(type, channel);
669
-
670
- // Emit first, then return the callback
671
- if (channel !== null) { // Do not emit or "unsubscribe" something if there was no channel to unsubscribe from
672
- self.emit(type, channel, count);
673
- if (type === 'subscribe' || type === 'psubscribe') {
674
- self.subscription_set[type + '_' + channel] = channel;
675
- } else {
676
- type = type === 'unsubscribe' ? 'subscribe' : 'psubscribe'; // Make types consistent
677
- delete self.subscription_set[type + '_' + channel];
678
- }
679
- }
680
-
681
- if (command_obj.args.length === 1 || self.sub_commands_left === 1 || command_obj.args.length === 0 && (count === 0 || channel === null)) {
682
- if (count === 0) { // unsubscribed from all channels
683
- var running_command;
684
- var i = 1;
685
- self.pub_sub_mode = 0; // Deactivating pub sub mode
686
- // This should be a rare case and therefore handling it this way should be good performance wise for the general case
687
- while (running_command = self.command_queue.get(i)) {
688
- if (SUBSCRIBE_COMMANDS[running_command.command]) {
689
- self.pub_sub_mode = i; // Entering pub sub mode again
690
- break;
691
- }
692
- i++;
693
- }
694
- }
695
- self.command_queue.shift();
696
- if (typeof command_obj.callback === 'function') {
697
- // TODO: The current return value is pretty useless.
698
- // Evaluate to change this in v.4 to return all subscribed / unsubscribed channels in an array including the number of channels subscribed too
699
- command_obj.callback(null, channel);
700
- }
701
- self.sub_commands_left = 0;
702
- } else {
703
- if (self.sub_commands_left !== 0) {
704
- self.sub_commands_left--;
705
- } else {
706
- self.sub_commands_left = command_obj.args.length ? command_obj.args.length - 1 : count;
707
- }
708
- }
709
- }
710
-
711
- function return_pub_sub (self, reply) {
712
- var type = reply[0].toString();
713
- if (type === 'message') { // channel, message
714
- if (!self.options.return_buffers || self.message_buffers) { // backwards compatible. Refactor this in v.4 to always return a string on the normal emitter
715
- self.emit('message', reply[1].toString(), reply[2].toString());
716
- self.emit('message_buffer', reply[1], reply[2]);
717
- self.emit('messageBuffer', reply[1], reply[2]);
718
- } else {
719
- self.emit('message', reply[1], reply[2]);
720
- }
721
- } else if (type === 'pmessage') { // pattern, channel, message
722
- if (!self.options.return_buffers || self.message_buffers) { // backwards compatible. Refactor this in v.4 to always return a string on the normal emitter
723
- self.emit('pmessage', reply[1].toString(), reply[2].toString(), reply[3].toString());
724
- self.emit('pmessage_buffer', reply[1], reply[2], reply[3]);
725
- self.emit('pmessageBuffer', reply[1], reply[2], reply[3]);
726
- } else {
727
- self.emit('pmessage', reply[1], reply[2], reply[3]);
728
- }
729
- } else {
730
- subscribe_unsubscribe(self, reply, type);
731
- }
732
- }
733
-
734
- RedisClient.prototype.return_reply = function (reply) {
735
- if (this.monitoring) {
736
- var replyStr;
737
- if (this.buffers && Buffer.isBuffer(reply)) {
738
- replyStr = reply.toString();
739
- } else {
740
- replyStr = reply;
741
- }
742
- // If in monitor mode, all normal commands are still working and we only want to emit the streamlined commands
743
- if (typeof replyStr === 'string' && utils.monitor_regex.test(replyStr)) {
744
- var timestamp = replyStr.slice(0, replyStr.indexOf(' '));
745
- var args = replyStr.slice(replyStr.indexOf('"') + 1, -1).split('" "').map(function (elem) {
746
- return elem.replace(/\\"/g, '"');
747
- });
748
- this.emit('monitor', timestamp, args, replyStr);
749
- return;
750
- }
751
- }
752
- if (this.pub_sub_mode === 0) {
753
- normal_reply(this, reply);
754
- } else if (this.pub_sub_mode !== 1) {
755
- this.pub_sub_mode--;
756
- normal_reply(this, reply);
757
- } else if (!(reply instanceof Array) || reply.length <= 2) {
758
- // Only PING and QUIT are allowed in this context besides the pub sub commands
759
- // Ping replies with ['pong', null|value] and quit with 'OK'
760
- normal_reply(this, reply);
761
- } else {
762
- return_pub_sub(this, reply);
763
- }
764
- };
765
-
766
- function handle_offline_command (self, command_obj) {
767
- var command = command_obj.command;
768
- var err, msg;
769
- if (self.closing || !self.enable_offline_queue) {
770
- command = command.toUpperCase();
771
- if (!self.closing) {
772
- if (self.stream.writable) {
773
- msg = 'The connection is not yet established and the offline queue is deactivated.';
774
- } else {
775
- msg = 'Stream not writeable.';
776
- }
777
- } else {
778
- msg = 'The connection is already closed.';
779
- }
780
- err = new errorClasses.AbortError({
781
- message: command + " can't be processed. " + msg,
782
- code: 'NR_CLOSED',
783
- command: command
784
- });
785
- if (command_obj.args.length) {
786
- err.args = command_obj.args;
787
- }
788
- utils.reply_in_order(self, command_obj.callback, err);
789
- } else {
790
- debug('Queueing ' + command + ' for next server connection.');
791
- self.offline_queue.push(command_obj);
792
- }
793
- self.should_buffer = true;
794
- }
795
-
796
- // Do not call internal_send_command directly, if you are not absolutly certain it handles everything properly
797
- // e.g. monitor / info does not work with internal_send_command only
798
- RedisClient.prototype.internal_send_command = function (command_obj) {
799
- var arg, prefix_keys;
800
- var i = 0;
801
- var command_str = '';
802
- var args = command_obj.args;
803
- var command = command_obj.command;
804
- var len = args.length;
805
- var big_data = false;
806
- var args_copy = new Array(len);
807
-
808
- if (process.domain && command_obj.callback) {
809
- command_obj.callback = process.domain.bind(command_obj.callback);
810
- }
811
-
812
- if (this.ready === false || this.stream.writable === false) {
813
- // Handle offline commands right away
814
- handle_offline_command(this, command_obj);
815
- return false; // Indicate buffering
816
- }
817
-
818
- for (i = 0; i < len; i += 1) {
819
- if (typeof args[i] === 'string') {
820
- // 30000 seemed to be a good value to switch to buffers after testing and checking the pros and cons
821
- if (args[i].length > 30000) {
822
- big_data = true;
823
- args_copy[i] = Buffer.from(args[i], 'utf8');
824
- } else {
825
- args_copy[i] = args[i];
826
- }
827
- } else if (typeof args[i] === 'object') { // Checking for object instead of Buffer.isBuffer helps us finding data types that we can't handle properly
828
- if (args[i] instanceof Date) { // Accept dates as valid input
829
- args_copy[i] = args[i].toString();
830
- } else if (Buffer.isBuffer(args[i])) {
831
- args_copy[i] = args[i];
832
- command_obj.buffer_args = true;
833
- big_data = true;
834
- } else {
835
- var invalidArgError = new Error(
836
- 'node_redis: The ' + command.toUpperCase() + ' command contains a invalid argument type.\n' +
837
- 'Only strings, dates and buffers are accepted. Please update your code to use valid argument types.'
838
- );
839
- invalidArgError.command = command_obj.command.toUpperCase();
840
- if (command_obj.args && command_obj.args.length) {
841
- invalidArgError.args = command_obj.args;
842
- }
843
- if (command_obj.callback) {
844
- command_obj.callback(invalidArgError);
845
- return false;
846
- }
847
- throw invalidArgError;
848
- }
849
- } else if (typeof args[i] === 'undefined') {
850
- var undefinedArgError = new Error(
851
- 'node_redis: The ' + command.toUpperCase() + ' command contains a invalid argument type of "undefined".\n' +
852
- 'Only strings, dates and buffers are accepted. Please update your code to use valid argument types.'
853
- );
854
- undefinedArgError.command = command_obj.command.toUpperCase();
855
- if (command_obj.args && command_obj.args.length) {
856
- undefinedArgError.args = command_obj.args;
857
- }
858
- // there is always a callback in this scenario
859
- command_obj.callback(undefinedArgError);
860
- return false;
861
- } else {
862
- // Seems like numbers are converted fast using string concatenation
863
- args_copy[i] = '' + args[i];
864
- }
865
- }
866
-
867
- if (this.options.prefix) {
868
- prefix_keys = commands.getKeyIndexes(command, args_copy);
869
- for (i = prefix_keys.pop(); i !== undefined; i = prefix_keys.pop()) {
870
- args_copy[i] = this.options.prefix + args_copy[i];
871
- }
872
- }
873
- if (this.options.rename_commands && this.options.rename_commands[command]) {
874
- command = this.options.rename_commands[command];
875
- }
876
- // Always use 'Multi bulk commands', but if passed any Buffer args, then do multiple writes, one for each arg.
877
- // This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer.
878
- command_str = '*' + (len + 1) + '\r\n$' + command.length + '\r\n' + command + '\r\n';
879
-
880
- if (big_data === false) { // Build up a string and send entire command in one write
881
- for (i = 0; i < len; i += 1) {
882
- arg = args_copy[i];
883
- command_str += '$' + Buffer.byteLength(arg) + '\r\n' + arg + '\r\n';
884
- }
885
- debug('Send ' + this.address + ' id ' + this.connection_id + ': ' + command_str);
886
- this.write(command_str);
887
- } else {
888
- debug('Send command (' + command_str + ') has Buffer arguments');
889
- this.fire_strings = false;
890
- this.write(command_str);
891
-
892
- for (i = 0; i < len; i += 1) {
893
- arg = args_copy[i];
894
- if (typeof arg === 'string') {
895
- this.write('$' + Buffer.byteLength(arg) + '\r\n' + arg + '\r\n');
896
- } else { // buffer
897
- this.write('$' + arg.length + '\r\n');
898
- this.write(arg);
899
- this.write('\r\n');
900
- }
901
- debug('send_command: buffer send ' + arg.length + ' bytes');
902
- }
903
- }
904
- if (command_obj.call_on_write) {
905
- command_obj.call_on_write();
906
- }
907
- // Handle `CLIENT REPLY ON|OFF|SKIP`
908
- // This has to be checked after call_on_write
909
- /* istanbul ignore else: TODO: Remove this as soon as we test Redis 3.2 on travis */
910
- if (this.reply === 'ON') {
911
- this.command_queue.push(command_obj);
912
- } else {
913
- // Do not expect a reply
914
- // Does this work in combination with the pub sub mode?
915
- if (command_obj.callback) {
916
- utils.reply_in_order(this, command_obj.callback, null, undefined, this.command_queue);
917
- }
918
- if (this.reply === 'SKIP') {
919
- this.reply = 'SKIP_ONE_MORE';
920
- } else if (this.reply === 'SKIP_ONE_MORE') {
921
- this.reply = 'ON';
922
- }
923
- }
924
- return !this.should_buffer;
925
- };
926
-
927
- RedisClient.prototype.write_strings = function () {
928
- var str = '';
929
- for (var command = this.pipeline_queue.shift(); command; command = this.pipeline_queue.shift()) {
930
- // Write to stream if the string is bigger than 4mb. The biggest string may be Math.pow(2, 28) - 15 chars long
931
- if (str.length + command.length > 4 * 1024 * 1024) {
932
- this.should_buffer = !this.stream.write(str);
933
- str = '';
934
- }
935
- str += command;
936
- }
937
- if (str !== '') {
938
- this.should_buffer = !this.stream.write(str);
939
- }
940
- };
941
-
942
- RedisClient.prototype.write_buffers = function () {
943
- for (var command = this.pipeline_queue.shift(); command; command = this.pipeline_queue.shift()) {
944
- this.should_buffer = !this.stream.write(command);
945
- }
946
- };
947
-
948
- RedisClient.prototype.write = function (data) {
949
- if (this.pipeline === false) {
950
- this.should_buffer = !this.stream.write(data);
951
- return;
952
- }
953
- this.pipeline_queue.push(data);
954
- };
955
-
956
- Object.defineProperty(exports, 'debugMode', {
957
- get: function () {
958
- return this.debug_mode;
959
- },
960
- set: function (val) {
961
- this.debug_mode = val;
962
- }
963
- });
964
-
965
- // Don't officially expose the command_queue directly but only the length as read only variable
966
- Object.defineProperty(RedisClient.prototype, 'command_queue_length', {
967
- get: function () {
968
- return this.command_queue.length;
969
- }
970
- });
971
-
972
- Object.defineProperty(RedisClient.prototype, 'offline_queue_length', {
973
- get: function () {
974
- return this.offline_queue.length;
975
- }
976
- });
977
-
978
- // Add support for camelCase by adding read only properties to the client
979
- // All known exposed snake_case variables are added here
980
- Object.defineProperty(RedisClient.prototype, 'retryDelay', {
981
- get: function () {
982
- return this.retry_delay;
983
- }
984
- });
985
-
986
- Object.defineProperty(RedisClient.prototype, 'retryBackoff', {
987
- get: function () {
988
- return this.retry_backoff;
989
- }
990
- });
991
-
992
- Object.defineProperty(RedisClient.prototype, 'commandQueueLength', {
993
- get: function () {
994
- return this.command_queue.length;
995
- }
996
- });
997
-
998
- Object.defineProperty(RedisClient.prototype, 'offlineQueueLength', {
999
- get: function () {
1000
- return this.offline_queue.length;
1001
- }
1002
- });
1003
-
1004
- Object.defineProperty(RedisClient.prototype, 'shouldBuffer', {
1005
- get: function () {
1006
- return this.should_buffer;
1007
- }
1008
- });
1009
-
1010
- Object.defineProperty(RedisClient.prototype, 'connectionId', {
1011
- get: function () {
1012
- return this.connection_id;
1013
- }
1014
- });
1015
-
1016
- Object.defineProperty(RedisClient.prototype, 'serverInfo', {
1017
- get: function () {
1018
- return this.server_info;
1019
- }
1020
- });
1021
-
1022
- exports.createClient = function () {
1023
- return new RedisClient(unifyOptions.apply(null, arguments));
1024
- };
1025
- exports.RedisClient = RedisClient;
1026
- exports.print = utils.print;
1027
- exports.Multi = require('./lib/multi');
1028
- exports.AbortError = errorClasses.AbortError;
1029
- exports.RedisError = RedisErrors.RedisError;
1030
- exports.ParserError = RedisErrors.ParserError;
1031
- exports.ReplyError = RedisErrors.ReplyError;
1032
- exports.AggregateError = errorClasses.AggregateError;
1033
-
1034
- // Add all redis commands / node_redis api to the client
1035
- require('./lib/individualCommands');
1036
- require('./lib/extendedApi');
1037
-
1038
- //enables adding new commands (for modules and new commands)
1039
- exports.addCommand = exports.add_command = require('./lib/commands');