1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-04 15:02:09 +03:00

start of work towards v3 release

This commit is contained in:
Salakar
2020-02-09 02:05:21 +00:00
parent 1d8fa45689
commit c0cc0bfab4
18 changed files with 234 additions and 400 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
open_collective: node-redis

View File

@@ -11,3 +11,9 @@ coverage/
*.rdb
*.out
*.yml
CHANGELOG.md
CONTRIBUTING.md
CODE_OF_CONDUCT.md
.travis.yml
appveyor.yml
package-lock.json

View File

@@ -9,7 +9,6 @@ addons:
packages:
- g++-4.8
node_js:
- "4"
- "6"
- "8"
- "12"

View File

@@ -1,5 +1,34 @@
# Changelog
## v.3.0.0 - 09 Feb, 2020
This version is mainly a release to distribute all the unreleased changes on master since 2017 and additionally removes
a lot of old deprecated features and old internals in preparation for an upcoming modernization refactor (v4).
### Breaking Changes
- Dropped support for Node.js < 6
- Dropped support for `hiredis` (no longer required)
- Removed previously deprecated `drain` event
- Removed previously deprecated `idle` event
- Removed previously deprecated `parser` option
- Removed previously deprecated `max_delay` option
- Removed previously deprecated `max_attempts` option
- Removed previously deprecated `socket_no_delay` option
### Bug Fixes
- Removed development files from published package (#1370)
- Duplicate function now allows db param to be passed (#1311)
### Features
- Upgraded to latest `redis-commands` package
- Upgraded to latest `redis-parser` package, v3.0.0, which brings performance improvements
- Replaced `double-ended-queue` with `denque`, which brings performance improvements
- Add timestamps to debug traces
- Add `socket_initial_delay` option for `socket.setKeepAlive` (#1396)
## v.2.8.0 - 31 Jul, 2017
Features

46
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at `redis @ invertase.io`. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

1
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1 @@
<!-- TODO -->

View File

@@ -1,6 +1,6 @@
LICENSE - "MIT License"
MIT License
Copyright (c) 2016 by NodeRedis
Copyright (c) Node Redis contributors.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation

View File

@@ -1,10 +1,21 @@
redis - a node.js redis client
===========================
<p align="center">
<a href="https://github.com/noderedis/node-redis/">
<img width="160px" src="https://static.invertase.io/assets/node_redis_logo.png" />
</a>
<h2 align="center">Node Redis</h2>
A high performance Node.js Redis client.
</p>
[![Build Status](https://travis-ci.org/NodeRedis/node_redis.svg?branch=master)](https://travis-ci.org/NodeRedis/node_redis)
[![Coverage Status](https://coveralls.io/repos/NodeRedis/node_redis/badge.svg?branch=)](https://coveralls.io/r/NodeRedis/node_redis?branch=)
[![Windows Tests](https://img.shields.io/appveyor/ci/BridgeAR/node-redis/master.svg?label=Windows%20Tests)](https://ci.appveyor.com/project/BridgeAR/node-redis/branch/master)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/NodeRedis/node_redis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
<p align="center">
<a href="https://www.npmjs.com/package/redis"><img src="https://img.shields.io/npm/dm/redis.svg?style=flat-square" alt="NPM downloads"></a>
<a href="https://www.npmjs.com/package/redis"><img src="https://img.shields.io/npm/v/redis.svg?style=flat-square" alt="NPM version"></a>
<a href="https://travis-ci.org/NodeRedis/node_redis"><img src="https://travis-ci.org/NodeRedis/node_redis.svg?style=flat-square&branch=master" alt="Build Status" /></a>
<a href="https://coveralls.io/r/NodeRedis/node_redis?branch="><img src="https://coveralls.io/repos/NodeRedis/node_redis/badge.svg?style=flat-square&branch=" alt="Coverage Status" /></a>
<a href="https://ci.appveyor.com/project/BridgeAR/node-redis/branch/master"><img src="https://img.shields.io/appveyor/ci/BridgeAR/node-redis/master.svg?style=flat-square&label=Windows%20Tests" alt="Windows Tests" /></a>
<a href="https://twitter.com/rnfirebase"><img src="https://img.shields.io/twitter/follow/NodeRedis.svg?style=flat-square&colorA=1da1f2&colorB=&label=Follow%20on%20Twitter" alt="Follow on Twitter"></a>
</p>
---
This is a complete and feature rich Redis client for node.js. __It supports all
Redis commands__ and focuses on high performance.
@@ -195,30 +206,11 @@ So please attach the error listener to node_redis.
`client` will emit `end` when an established Redis server connection has closed.
### "drain" (deprecated)
`client` will emit `drain` when the TCP connection to the Redis server has been
buffering, but is now writable. This event can be used to stream commands in to
Redis and adapt to backpressure.
If the stream is buffering `client.should_buffer` is set to true. Otherwise the
variable is always set to false. That way you can decide when to reduce your
send rate and resume sending commands when you get `drain`.
You can also check the return value of each command as it will also return the
backpressure indicator (deprecated). If false is returned the stream had to
buffer.
### "warning"
`client` will emit `warning` when password was set but none is needed and if a
deprecated option / function / similar is used.
### "idle" (deprecated)
`client` will emit `idle` when there are no outstanding commands that are
awaiting a response.
## redis.createClient()
If you have `redis-server` running on the same machine as node, then the
defaults for port and host are probably fine and you don't need to supply any
@@ -242,17 +234,13 @@ __Note:__ Using `'rediss://...` for the protocol in a `redis_url` will enable a
| port | 6379 | Port of the Redis server |
| path | null | The UNIX socket string of the Redis server |
| url | null | The URL of the Redis server. Format: `[redis[s]:]//[[user][:password@]][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]` (More info avaliable at [IANA](http://www.iana.org/assignments/uri-schemes/prov/redis)). |
| parser | javascript | __Deprecated__ Use either the built-in JS parser [`javascript`]() or the native [`hiredis`]() parser. __Note__ `node_redis` < 2.6 uses hiredis as default if installed. This changed in v.2.6.0. |
| string_numbers | null | Set to `true`, `node_redis` will return Redis number values as Strings instead of javascript Numbers. Useful if you need to handle big numbers (above `Number.MAX_SAFE_INTEGER === 2^53`). Hiredis is incapable of this behavior, so setting this option to `true` will result in the built-in javascript parser being used no matter the value of the `parser` option. |
| return_buffers | false | If set to `true`, then all replies will be sent to callbacks as Buffers instead of Strings. |
| detect_buffers | false | If set to `true`, then replies will be sent to callbacks as Buffers. This option lets you switch between Buffers and Strings on a per-command basis, whereas `return_buffers` applies to every command on a client. __Note__: This doesn't work properly with the pubsub mode. A subscriber has to either always return Strings or Buffers. |
| socket_keepalive | true | If set to `true`, the keep-alive functionality is enabled on the underlying socket. |
| socket_initialdelay | 0 | Initial Delay in milliseconds, and this will also behave the interval keep alive message sending to Redis. |
| socket_initial_delay | 0 | Initial Delay in milliseconds, and this will also behave the interval keep alive message sending to Redis. |
| no_ready_check | false | When a connection is established to the Redis server, the server might still be loading the database from disk. While loading, the server will not respond to any commands. To work around this, `node_redis` has a "ready check" which sends the `INFO` command to the server. The response from the `INFO` command indicates whether the server is ready for more commands. When ready, `node_redis` emits a `ready` event. Setting `no_ready_check` to `true` will inhibit this check. |
| enable_offline_queue | true | By default, if there is no active connection to the Redis server, commands are added to a queue and are executed once the connection has been established. Setting `enable_offline_queue` to `false` will disable this feature and the callback will be executed immediately with an error, or an error will be emitted if no callback is specified. |
| retry_max_delay | null | __Deprecated__ _Please use `retry_strategy` instead._ By default, every time the client tries to connect and fails, the reconnection delay almost doubles. This delay normally grows infinitely, but setting `retry_max_delay` limits it to the maximum value provided in milliseconds. |
| connect_timeout | 3600000 | __Deprecated__ _Please use `retry_strategy` instead._ Setting `connect_timeout` limits the total time for the client to connect and reconnect. The value is provided in milliseconds and is counted from the moment a new client is created or from the time the connection is lost. The last retry is going to happen exactly at the timeout time. Default is to try connecting until the default system socket timeout has been exceeded and to try reconnecting until 1h has elapsed. |
| max_attempts | 0 | __Deprecated__ _Please use `retry_strategy` instead._ By default, a client will try reconnecting until connected. Setting `max_attempts` limits total amount of connection attempts. Setting this to 1 will prevent any reconnect attempt. |
| retry_unfulfilled_commands | false | If set to `true`, all commands that were unfulfilled while the connection is lost will be retried after the connection has been reestablished. Use this with caution if you use state altering commands (e.g. `incr`). This is especially useful if you use blocking commands. |
| password | null | If set, client will run Redis auth command on connect. Alias `auth_pass` __Note__ `node_redis` < 2.5 must use `auth_pass` |
| db | null | If set, client will run Redis `select` command on connect. |
@@ -473,12 +461,6 @@ client.hgetall("hosts", function (err, obj) {
});
```
Output:
```js
{ mjr: '1', another: '23', home: '1234' }
```
### client.hmset(hash, obj[, callback])
Multiple values in a hash can be set by supplying an object:
@@ -770,7 +752,7 @@ clients.alterer = clients.watcher.duplicate();
clients.watcher.watch('foo',function(err) {
if (err) { throw err; }
//if you comment out the next line, the transaction will work
clients.alterer.set('foo',Math.random(), (err) => {if (err) { throw err; }})
clients.alterer.set('foo',Math.random(), (err) => {if (err) { throw err; }});
//using a setTimeout here to ensure that the MULTI/EXEC will come after the SET.
//Normally, you would use a callback to ensure order, but I want the above SET command
@@ -897,8 +879,8 @@ the second word as first parameter:
```js
client.script('load', 'return 1');
client.multi().script('load', 'return 1').exec(...);
client.multi([['script', 'load', 'return 1']]).exec(...);
client.multi().script('load', 'return 1').exec();
client.multi([['script', 'load', 'return 1']]).exec();
```
## client.duplicate([options][, callback])
@@ -1082,6 +1064,7 @@ ReplyError: ERR wrong number of arguments for 'set' command
```
## How to Contribute
- Open a pull request or an issue about what you want to implement / change. We're glad for any help!
- Please be aware that we'll only accept fully tested code.
@@ -1097,23 +1080,3 @@ contributed to `node_redis` too. Thanks to all of them!
## License
[MIT](LICENSE)
### Consolidation: It's time for celebration
Right now there are two great redis clients around and both have some advantages
above each other. We speak about ioredis and node_redis. So after talking to
each other about how we could improve in working together we (that is @luin and
@BridgeAR) decided to work towards a single library on the long run. But step by
step.
First of all, we want to split small parts of our libraries into others so that
we're both able to use the same code. Those libraries are going to be maintained
under the NodeRedis organization. This is going to reduce the maintenance
overhead, allows others to use the very same code, if they need it and it's way
easyer for others to contribute to both libraries.
We're very happy about this step towards working together as we both want to
give you the best redis experience possible.
If you want to join our cause by help maintaining something, please don't
hesitate to contact either one of us.

View File

@@ -3,7 +3,6 @@
# Test against these versions of Node.js.
environment:
matrix:
- nodejs_version: "4"
- nodejs_version: "6"
- nodejs_version: "8"
- nodejs_version: "10"

141
index.js
View File

@@ -67,38 +67,16 @@ function RedisClient (options, stream) {
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;
}
}
this.connection_options = cnx_options;
this.connection_id = RedisClient.connection_id++;
this.connected = false;
this.ready = false;
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;
}
if (options.socket_initialdelay === undefined) {
options.socket_initialdelay = 0;
if (options.socket_initial_delay === undefined) {
options.socket_initial_delay = 0;
// set default to 0, which is aligned to https://nodejs.org/api/net.html#net_socket_setkeepalive_enable_initialdelay
}
for (var command in options.rename_commands) {
@@ -116,14 +94,6 @@ function RedisClient (options, stream) {
this.handle_reply = handle_detect_buffers_reply;
}
this.should_buffer = false;
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' +
'To reduce the number of options and to improve the reconnection handling please use the new `retry_strategy` option instead.\n' +
'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
@@ -131,14 +101,6 @@ function RedisClient (options, stream) {
// 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.'
);
}
this.initialize_retry_vars();
this.pub_sub_mode = 0;
this.subscription_set = {};
@@ -158,17 +120,7 @@ function RedisClient (options, stream) {
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.'
);
} else if ((event === 'message_buffer' || event === 'pmessage_buffer' || event === 'messageBuffer' || event === 'pmessageBuffer') && !this.buffers && !this.message_buffers) {
if ((event === 'message_buffer' || event === 'pmessage_buffer' || event === 'messageBuffer' || event === 'pmessageBuffer') && !this.buffers && !this.message_buffers) {
this.reply_parser.optionReturnBuffers = true;
this.message_buffers = true;
this.handle_reply = handle_detect_buffers_reply;
@@ -264,7 +216,6 @@ RedisClient.prototype.create_stream = function () {
// 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();
});
this.stream.on('error', function (err) {
@@ -283,9 +234,7 @@ RedisClient.prototype.create_stream = 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) {
@@ -387,7 +336,7 @@ RedisClient.prototype.on_error = function (err) {
this.connected = false;
this.ready = false;
// Only emit the error if the retry_stategy option is not set
// Only emit the error if the retry_strategy option is not set
if (!this.options.retry_strategy) {
this.emit('error', err);
}
@@ -402,7 +351,7 @@ RedisClient.prototype.on_connect = function () {
this.connected = true;
this.ready = false;
this.emitted_end = false;
this.stream.setKeepAlive(this.options.socket_keepalive, this.options.socket_initialdelay);
this.stream.setKeepAlive(this.options.socket_keepalive, this.options.socket_initial_delay);
this.stream.setTimeout(0);
this.emit('connect');
@@ -607,18 +556,31 @@ RedisClient.prototype.connection_gone = function (why, error) {
if (this.retry_delay instanceof Error) {
error = this.retry_delay;
}
var errorMessage = 'Redis connection in broken state: ';
if (this.retry_totaltime >= this.connect_timeout) {
errorMessage += 'connection timeout exceeded.';
} else {
errorMessage += 'maximum connection attempts exceeded.';
}
this.flush_and_error({
message: 'Stream connection ended and command aborted.',
code: 'NR_CLOSED'
message: errorMessage,
code: 'CONNECTION_BROKEN',
}, {
error: error
});
var retryError = new Error(errorMessage);
retryError.code = 'CONNECTION_BROKEN';
if (error) {
retryError.origin = error;
}
this.end(false);
this.emit('error', retryError);
return;
}
}
if (this.max_attempts !== 0 && this.attempts >= this.max_attempts || this.retry_totaltime >= this.connect_timeout) {
if (this.retry_totaltime >= this.connect_timeout) {
var message = 'Redis connection in broken state: ';
if (this.retry_totaltime >= this.connect_timeout) {
message += 'connection timeout exceeded.';
@@ -637,8 +599,8 @@ RedisClient.prototype.connection_gone = function (why, error) {
if (error) {
err.origin = error;
}
this.emit('error', err);
this.end(false);
this.emit('error', err);
return;
}
@@ -656,15 +618,12 @@ RedisClient.prototype.connection_gone = function (why, error) {
});
}
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) {
if (this.retry_totaltime + this.retry_delay > this.connect_timeout) {
// Do not exceed the maximum
this.retry_delay = this.connect_timeout - this.retry_totaltime;
}
debug('Retry connection in ' + this.retry_delay + ' ms');
this.retry_timer = setTimeout(retry_connection, this.retry_delay, this, error);
};
@@ -693,16 +652,9 @@ RedisClient.prototype.return_error = function (err) {
};
RedisClient.prototype.drain = function () {
this.emit('drain');
this.should_buffer = false;
};
RedisClient.prototype.emit_idle = function () {
if (this.command_queue.length === 0 && this.pub_sub_mode === 0) {
this.emit('idle');
}
};
function normal_reply (self, reply) {
var command_obj = self.command_queue.shift();
if (typeof command_obj.callback === 'function') {
@@ -752,7 +704,7 @@ function subscribe_unsubscribe (self, reply, type) {
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
// Evaluate to change this in v.4 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;
@@ -768,7 +720,7 @@ function subscribe_unsubscribe (self, reply, type) {
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
if (!self.options.return_buffers || self.message_buffers) { // backwards compatible. Refactor this in v.4 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]);
@@ -776,7 +728,7 @@ function return_pub_sub (self, reply) {
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
if (!self.options.return_buffers || self.message_buffers) { // backwards compatible. Refactor this in v.4 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]);
@@ -884,32 +836,39 @@ RedisClient.prototype.internal_send_command = function (command_obj) {
} 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.'
var invalidArgError = new Error(
'node_redis: The ' + command.toUpperCase() + ' command contains a invalid argument type.\n' +
'Only strings, dates and buffers are accepted. Please update your code to use valid argument types.'
);
args_copy[i] = args[i].toString(); // Backwards compatible :/
invalidArgError.command = command_obj.command.toUpperCase();
if (command_obj.args && command_obj.args.length) {
invalidArgError.args = command_obj.args;
}
if (command_obj.callback) {
command_obj.callback(invalidArgError);
return false;
}
throw invalidArgError;
}
} 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.'
var undefinedArgError = new Error(
'node_redis: The ' + command.toUpperCase() + ' command contains a invalid argument type of "undefined".\n' +
'Only strings, dates and buffers are accepted. Please update your code to use valid argument types.'
);
args_copy[i] = 'undefined'; // Backwards compatible :/
undefinedArgError.command = command_obj.command.toUpperCase();
if (command_obj.args && command_obj.args.length) {
undefinedArgError.args = command_obj.args;
}
if (command_obj.callback) {
command_obj.callback(undefinedArgError);
return false;
}
throw undefinedArgError;
} else {
// Seems like numbers are converted fast using string concatenation
args_copy[i] = '' + args[i];

View File

@@ -1,7 +1,7 @@
{
"name": "redis",
"version": "2.8.0",
"description": "Redis client library",
"description": "A high performance Redis client.",
"keywords": [
"database",
"redis",
@@ -14,6 +14,16 @@
"backpressure"
],
"author": "Matt Ranney <mjr@ranney.com>",
"contributors": [
{
"name": "Mike Diarmid (Salakar)",
"url": "https://github.com/salakar"
},
{
"name": "Ruben Bridgewater (BridgeAR)",
"url": "https://github.com/BridgeAR"
}
],
"license": "MIT",
"main": "./index.js",
"scripts": {
@@ -31,30 +41,34 @@
"redis-parser": "^3.0.0"
},
"engines": {
"node": ">=4.0.0"
"node": ">=6"
},
"devDependencies": {
"bluebird": "^3.0.2",
"bluebird": "^3.7.2",
"coveralls": "^2.11.2",
"eslint": "^4.2.0",
"eslint": "^6.8.0",
"intercept-stdout": "~0.1.2",
"metrics": "^0.1.9",
"mocha": "^3.1.2",
"nyc": "^10.0.0",
"tcp-port-used": "^0.1.2",
"uuid": "^2.0.1",
"win-spawn": "^2.0.0"
"metrics": "^0.1.21",
"mocha": "^4.1.0",
"nyc": "^14.1.1",
"tcp-port-used": "^1.0.1",
"uuid": "^3.4.0",
"cross-spawn": "^6.0.5"
},
"repository": {
"type": "git",
"url": "git://github.com/NodeRedis/node_redis.git"
"url": "git://github.com/NodeRedis/node-redis.git"
},
"bugs": {
"url": "https://github.com/NodeRedis/node_redis/issues"
"url": "https://github.com/NodeRedis/node-redis/issues"
},
"homepage": "https://github.com/NodeRedis/node_redis",
"homepage": "https://github.com/NodeRedis/node-redis",
"directories": {
"example": "examples",
"test": "test"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-redis"
}
}

View File

@@ -192,7 +192,7 @@ describe('client authentication', function () {
client = redis.createClient.apply(null, args);
var async = true;
client.auth(undefined, function (err, res) {
client.auth('undefined', function (err, res) {
assert.strictEqual(err.message, 'ERR invalid password');
assert.strictEqual(err.command, 'AUTH');
assert.strictEqual(res, undefined);

View File

@@ -45,22 +45,16 @@ describe("The 'hset' method", function () {
});
});
it('warns if someone passed a array either as field or as value', function (done) {
it('errors if someone passed a array either as field or as value', function (done) {
var hash = 'test hash';
var field = 'array';
// This would be converted to "array contents" but if you use more than one entry,
// it'll result in e.g. "array contents,second content" and this is not supported and considered harmful
var value = ['array contents'];
client.on('warning', function (msg) {
assert.strictEqual(
msg,
'Deprecated: The HMSET command contains a argument of type Array.\n' +
'This is converted to "array contents" 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.'
);
done();
});
try {
client.HMSET(hash, field, value);
} catch (error) {
assert(/node_redis: The HMSET command contains a invalid argument type./.test(error.message));
done();
}
});
it('does not error when a buffer and date are set as values on the same hash', function (done) {

View File

@@ -140,10 +140,14 @@ describe("The 'set' method", function () {
});
});
// TODO: This test has to be refactored from v.3.0 on to expect an error instead
it("converts null to 'null'", function (done) {
it('errors if null value is passed', function (done) {
try {
client.set('foo', null);
client.get('foo', helper.isString('null', done));
assert(false);
} catch (error) {
assert(/The SET command contains a invalid argument type./.test(error.message));
}
client.get('foo', helper.isNull(done));
});
it('emit an error with only the key set', function (done) {

View File

@@ -152,40 +152,6 @@ describe('connection tests', function () {
describe('using ' + parser + ' and ' + ip, function () {
describe('on lost connection', function () {
it('emit an error after max retry attempts and do not try to reconnect afterwards', function (done) {
var maxAttempts = 3;
var options = {
parser: parser,
maxAttempts: maxAttempts
};
client = redis.createClient(options);
assert.strictEqual(client.retryBackoff, 1.7);
assert.strictEqual(client.retryDelay, 200);
assert.strictEqual(Object.keys(options).length, 2);
var calls = 0;
client.once('ready', function () {
helper.killConnection(client);
});
client.on('reconnecting', function (params) {
calls++;
});
client.on('error', function (err) {
if (/Redis connection in broken state: maximum connection attempts.*?exceeded./.test(err.message)) {
process.nextTick(function () { // End is called after the error got emitted
assert.strictEqual(calls, maxAttempts - 1);
assert.strictEqual(client.emitted_end, true);
assert.strictEqual(client.connected, false);
assert.strictEqual(client.ready, false);
assert.strictEqual(client.closing, true);
done();
});
}
});
});
it('emit an error after max retry timeout and do not try to reconnect afterwards', function (done) {
// TODO: Investigate why this test fails with windows. Reconnect is only triggered once
if (process.platform === 'win32') this.skip();
@@ -271,16 +237,11 @@ describe('connection tests', function () {
});
it('retryStrategy used to reconnect with individual error', function (done) {
var text = '';
var unhookIntercept = intercept(function (data) {
text += data;
return '';
});
client = redis.createClient({
retryStrategy: function (options) {
if (options.totalRetryTime > 150) {
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, 'Stream connection ended and command aborted.');
assert.strictEqual(err.message, 'Redis connection in broken state: maximum connection attempts exceeded.');
assert.strictEqual(err.origin.message, 'Connection timeout');
done();
});
@@ -289,18 +250,8 @@ describe('connection tests', function () {
}
return Math.min(options.attempt * 25, 200);
},
maxAttempts: 5,
retryMaxDelay: 123,
port: 9999
});
process.nextTick(function () {
assert.strictEqual(
text,
'node_redis: WARNING: You activated the retry_strategy and max_attempts at the same time. This is not possible and max_attempts will be ignored.\n' +
'node_redis: 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.\n'
);
unhookIntercept();
});
});
it('retry_strategy used to reconnect', function (done) {
@@ -308,8 +259,8 @@ describe('connection tests', function () {
retry_strategy: function (options) {
if (options.total_retry_time > 150) {
client.set('foo', 'bar', function (err, res) {
assert.strictEqual(err.message, 'Stream connection ended and command aborted.');
assert.strictEqual(err.code, 'NR_CLOSED');
assert.strictEqual(err.message, 'Redis connection in broken state: maximum connection attempts exceeded.');
assert.strictEqual(err.code, 'CONNECTION_BROKEN');
assert.strictEqual(err.origin.code, 'ECONNREFUSED');
done();
});
@@ -337,11 +288,13 @@ describe('connection tests', function () {
client.stream.destroy();
}, 50);
client.on('error', function (err) {
assert.strictEqual(err.code, 'NR_CLOSED');
assert.strictEqual(err.message, 'Stream connection ended and command aborted.');
if (err instanceof redis.AbortError) {
assert.strictEqual(err.message, 'Redis connection in broken state: maximum connection attempts exceeded.');
assert.strictEqual(err.code, 'CONNECTION_BROKEN');
unhookIntercept();
redis.debugMode = false;
done();
}
});
});
});

View File

@@ -4,7 +4,7 @@
var config = require('./config');
var fs = require('fs');
var path = require('path');
var spawn = require('win-spawn');
var spawn = require('cross-spawn');
var tcpPortUsed = require('tcp-port-used');
var bluebird = require('bluebird');

View File

@@ -219,17 +219,22 @@ describe("The 'multi' method", function () {
client = redis.createClient({
host: 'somewhere',
port: 6379,
max_attempts: 1
retry_strategy: function (options) {
if (options.attempt > 1) {
// End reconnecting with built in error
return undefined;
}
}
});
client.on('error', function (err) {
if (/Redis connection in broken state/.test(err.message)) {
if (/Redis connection to somewhere:6379 failed/.test(err.origin.message)) {
done();
}
});
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec(function (err, res) {
assert(/Redis connection in broken state/.test(err.message));
assert(/Redis connection in broken state: maximum connection attempts exceeded/.test(err.message));
assert.strictEqual(err.errors.length, 2);
assert.strictEqual(err.errors[0].args.length, 2);
});
@@ -239,12 +244,17 @@ describe("The 'multi' method", function () {
client = redis.createClient({
host: 'somewhere',
port: 6379,
max_attempts: 1
retry_strategy: function (options) {
if (options.attempt > 1) {
// End reconnecting with built in error
return undefined;
}
}
});
client.on('error', function (err) {
// Results in multiple done calls if test fails
if (/Redis connection in broken state/.test(err.message)) {
if (/Redis connection to somewhere:6379 failed/.test(err.origin.message)) {
done();
}
});

View File

@@ -707,25 +707,6 @@ describe('The node_redis client', function () {
});
});
describe('idle', function () {
it('emits idle as soon as there are no outstanding commands', function (done) {
var end = helper.callFuncAfter(done, 2);
client.on('warning', function (msg) {
assert.strictEqual(
msg,
'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'
);
end();
});
client.on('idle', function onIdle () {
client.removeListener('idle', onIdle);
client.get('foo', helper.isString('bar', end));
});
client.set('foo', 'bar');
});
});
describe('utf8', function () {
it('handles utf-8 keys', function (done) {
var utf8_sample = 'ಠ_ಠ';
@@ -793,111 +774,6 @@ describe('The node_redis client', function () {
// });
});
describe('socket_nodelay', function () {
describe('true', function () {
var args = config.configureClient(parser, ip, {
socket_nodelay: true
});
it("fires client.on('ready')", function (done) {
client = redis.createClient.apply(null, args);
client.on('ready', function () {
assert.strictEqual(true, client.options.socket_nodelay);
client.quit(done);
});
});
it('client is functional', function (done) {
client = redis.createClient.apply(null, args);
client.on('ready', function () {
assert.strictEqual(true, client.options.socket_nodelay);
client.set(['set key 1', 'set val'], helper.isString('OK'));
client.set(['set key 2', 'set val'], helper.isString('OK'));
client.get(['set key 1'], helper.isString('set val'));
client.get(['set key 2'], helper.isString('set val'));
client.quit(done);
});
});
});
describe('false', function () {
var args = config.configureClient(parser, ip, {
socket_nodelay: false
});
it("fires client.on('ready')", function (done) {
client = redis.createClient.apply(null, args);
client.on('ready', function () {
assert.strictEqual(false, client.options.socket_nodelay);
client.quit(done);
});
});
it('client is functional', function (done) {
client = redis.createClient.apply(null, args);
client.on('ready', function () {
assert.strictEqual(false, client.options.socket_nodelay);
client.set(['set key 1', 'set val'], helper.isString('OK'));
client.set(['set key 2', 'set val'], helper.isString('OK'));
client.get(['set key 1'], helper.isString('set val'));
client.get(['set key 2'], helper.isString('set val'));
client.quit(done);
});
});
});
describe('defaults to true', function () {
it("fires client.on('ready')", function (done) {
client = redis.createClient.apply(null, args);
client.on('ready', function () {
assert.strictEqual(true, client.options.socket_nodelay);
client.quit(done);
});
});
it('client is functional', function (done) {
client = redis.createClient.apply(null, args);
client.on('ready', function () {
assert.strictEqual(true, client.options.socket_nodelay);
client.set(['set key 1', 'set val'], helper.isString('OK'));
client.set(['set key 2', 'set val'], helper.isString('OK'));
client.get(['set key 1'], helper.isString('set val'));
client.get(['set key 2'], helper.isString('set val'));
client.quit(done);
});
});
});
});
describe('retry_max_delay', function () {
it('sets upper bound on how long client waits before reconnecting', function (done) {
var time;
var timeout = process.platform !== 'win32' ? 20 : 100;
client = redis.createClient.apply(null, config.configureClient(parser, ip, {
retry_max_delay: 1 // ms
}));
client.on('ready', function () {
if (this.times_connected === 1) {
this.stream.end();
time = Date.now();
} else {
done();
}
});
client.on('reconnecting', function () {
time = Date.now() - time;
assert(time < timeout, 'The reconnect should not have taken longer than ' + timeout + ' but it took ' + time);
});
client.on('error', function (err) {
// This is rare but it might be triggered.
// So let's have a robust test
assert.strictEqual(err.code, 'ECONNRESET');
});
});
});
describe('protocol error', function () {
it('should gracefully recover and only fail on the already send commands', function (done) {
@@ -932,33 +808,9 @@ describe('The node_redis client', function () {
describe('enable_offline_queue', function () {
describe('true', function () {
it('should emit drain if offline queue is flushed and nothing to buffer', function (done) {
client = redis.createClient({
parser: parser,
no_ready_check: true
});
var end = helper.callFuncAfter(done, 3);
client.set('foo', 'bar');
client.get('foo', end);
client.on('warning', function (msg) {
assert.strictEqual(
msg,
'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.'
);
end();
});
client.on('drain', function () {
assert(client.offline_queue.length === 0);
end();
});
});
it('does not return an error and enqueues operation', function (done) {
client = redis.createClient(9999, null, {
max_attempts: 0,
parser: parser
});
client = redis.createClient(9999, null);
var finished = false;
client.on('error', function (e) {
// ignore, b/c expecting a "can't connect" error
@@ -980,8 +832,12 @@ describe('The node_redis client', function () {
it('enqueues operation and keep the queue while trying to reconnect', function (done) {
client = redis.createClient(9999, null, {
max_attempts: 4,
parser: parser
retry_strategy: function (options) {
if (options.attempt > 4) {
return undefined;
}
return 100;
},
});
var i = 0;