redis 2.6.0-2 → 2.6.3
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.
- package/README.md +100 -53
- package/changelog.md +40 -2
- package/index.js +34 -43
- package/lib/command.js +7 -13
- package/lib/commands.js +29 -2
- package/lib/customErrors.js +17 -49
- package/lib/extendedApi.js +2 -1
- package/lib/individualCommands.js +35 -33
- package/lib/multi.js +34 -43
- package/lib/utils.js +9 -7
- package/package.json +5 -4
- package/lib/rawObject.js +0 -8
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
redis - a node.js redis client
|
|
2
2
|
===========================
|
|
3
3
|
|
|
4
|
-
[](https://travis-ci.org/NodeRedis/node_redis)
|
|
5
5
|
[](https://coveralls.io/r/NodeRedis/node_redis?branch=)
|
|
6
6
|
[](https://ci.appveyor.com/project/BridgeAR/node-redis/branch/master)
|
|
7
7
|
[](https://gitter.im/NodeRedis/node_redis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
|
8
8
|
|
|
9
|
-
This is a complete and feature rich Redis client for node.js.
|
|
9
|
+
This is a complete and feature rich Redis client for node.js. __It supports all Redis commands__ and focuses on high performance.
|
|
10
10
|
|
|
11
11
|
Install with:
|
|
12
12
|
|
|
@@ -49,7 +49,7 @@ This will display:
|
|
|
49
49
|
mjr:~/work/node_redis (master)$
|
|
50
50
|
|
|
51
51
|
Note that the API is entirely asynchronous. To get data back from the server, you'll need to use a callback.
|
|
52
|
-
From v.2.6 on the API supports camelCase and
|
|
52
|
+
From v.2.6 on the API supports camelCase and snake_case and all options / variables / events etc. can be used either way.
|
|
53
53
|
It is recommended to use camelCase as this is the default for the Node.js landscape.
|
|
54
54
|
|
|
55
55
|
### Promises
|
|
@@ -182,15 +182,15 @@ __Tip:__ If the Redis server runs on the same machine as the client consider usi
|
|
|
182
182
|
| port | 6379 | Port of the Redis server |
|
|
183
183
|
| path | null | The UNIX socket string of the Redis server |
|
|
184
184
|
| url | null | The URL of the Redis server. Format: `[redis:]//[[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)). |
|
|
185
|
-
| parser |
|
|
186
|
-
| string_numbers | null
|
|
185
|
+
| 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. |
|
|
186
|
+
| 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. |
|
|
187
187
|
| return_buffers | false | If set to `true`, then all replies will be sent to callbacks as Buffers instead of Strings. |
|
|
188
188
|
| 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. |
|
|
189
189
|
| socket_keepalive | true | If set to `true`, the keep-alive functionality is enabled on the underlying socket. |
|
|
190
190
|
| 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. |
|
|
191
191
|
| 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. |
|
|
192
|
-
| retry_max_delay | null | 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. |
|
|
193
|
-
| connect_timeout | 3600000 | 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. |
|
|
192
|
+
| 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. |
|
|
193
|
+
| 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. |
|
|
194
194
|
| 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. |
|
|
195
195
|
| 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. |
|
|
196
196
|
| password | null | If set, client will run Redis auth command on connect. Alias `auth_pass` __Note__ `node_redis` < 2.5 must use `auth_pass` |
|
|
@@ -658,6 +658,33 @@ the second word as first parameter:
|
|
|
658
658
|
Duplicate all current options and return a new redisClient instance. All options passed to the duplicate function are going to replace the original option.
|
|
659
659
|
If you pass a callback, duplicate is going to wait until the client is ready and returns it in the callback. If an error occurs in the meanwhile, that is going to return an error instead in the callback.
|
|
660
660
|
|
|
661
|
+
One example of when to use duplicate() would be to accomodate the connection-
|
|
662
|
+
blocking redis commands BRPOP, BLPOP, and BRPOPLPUSH. If these commands
|
|
663
|
+
are used on the same redisClient instance as non-blocking commands, the
|
|
664
|
+
non-blocking ones may be queued up until after the blocking ones finish.
|
|
665
|
+
|
|
666
|
+
var Redis=require('redis');
|
|
667
|
+
var client = Redis.createClient();
|
|
668
|
+
var clientBlocking = client.duplicate();
|
|
669
|
+
|
|
670
|
+
var get = function() {
|
|
671
|
+
console.log("get called");
|
|
672
|
+
client.get("any_key",function() { console.log("get returned"); });
|
|
673
|
+
setTimeout( get, 1000 );
|
|
674
|
+
};
|
|
675
|
+
var brpop = function() {
|
|
676
|
+
console.log("brpop called");
|
|
677
|
+
clientBlocking.brpop("nonexistent", 5, function() {
|
|
678
|
+
console.log("brpop return");
|
|
679
|
+
setTimeout( brpop, 1000 );
|
|
680
|
+
});
|
|
681
|
+
};
|
|
682
|
+
get();
|
|
683
|
+
brpop();
|
|
684
|
+
|
|
685
|
+
Another reason to use duplicate() is when multiple DBs on the same server are
|
|
686
|
+
accessed via the redis SELECT command. Each DB could use its own connection.
|
|
687
|
+
|
|
661
688
|
## client.send_command(command_name[, [args][, callback]])
|
|
662
689
|
|
|
663
690
|
All Redis commands have been added to the `client` object. However, if new commands are introduced before this library is updated,
|
|
@@ -712,60 +739,80 @@ client.zadd(args, function (err, response) {
|
|
|
712
739
|
## Performance
|
|
713
740
|
|
|
714
741
|
Much effort has been spent to make `node_redis` as fast as possible for common
|
|
715
|
-
operations.
|
|
716
|
-
efficiency goes up.
|
|
717
|
-
|
|
718
|
-
Here are results of `multi_bench.js` which is similar to `redis-benchmark` from the Redis distribution.
|
|
719
|
-
|
|
720
|
-
hiredis parser (Lenovo T450s i7-5600U):
|
|
742
|
+
operations.
|
|
721
743
|
|
|
722
744
|
```
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
PING,
|
|
726
|
-
|
|
727
|
-
SET 4B str,
|
|
728
|
-
SET 4B
|
|
729
|
-
SET 4B buf,
|
|
730
|
-
|
|
731
|
-
GET 4B str,
|
|
732
|
-
GET 4B
|
|
733
|
-
GET 4B buf,
|
|
734
|
-
|
|
735
|
-
SET 4KiB str,
|
|
736
|
-
SET 4KiB
|
|
737
|
-
SET 4KiB buf,
|
|
738
|
-
|
|
739
|
-
GET 4KiB str,
|
|
740
|
-
GET 4KiB
|
|
741
|
-
GET 4KiB buf,
|
|
742
|
-
|
|
743
|
-
INCR,
|
|
744
|
-
|
|
745
|
-
LPUSH,
|
|
746
|
-
|
|
747
|
-
LRANGE 10,
|
|
748
|
-
|
|
749
|
-
LRANGE 100,
|
|
750
|
-
|
|
751
|
-
SET 4MiB str,
|
|
752
|
-
SET 4MiB
|
|
753
|
-
SET 4MiB buf,
|
|
754
|
-
|
|
755
|
-
GET 4MiB str,
|
|
756
|
-
GET 4MiB
|
|
757
|
-
GET 4MiB buf,
|
|
745
|
+
Lenovo T450s, i7-5600U and 12gb memory
|
|
746
|
+
clients: 1, NodeJS: 6.2.0, Redis: 3.2.0, parser: javascript, connected by: tcp
|
|
747
|
+
PING, 1/1 avg/max: 0.02/ 5.26 2501ms total, 46916 ops/sec
|
|
748
|
+
PING, batch 50/1 avg/max: 0.06/ 4.35 2501ms total, 755178 ops/sec
|
|
749
|
+
SET 4B str, 1/1 avg/max: 0.02/ 4.75 2501ms total, 40856 ops/sec
|
|
750
|
+
SET 4B str, batch 50/1 avg/max: 0.11/ 1.51 2501ms total, 432727 ops/sec
|
|
751
|
+
SET 4B buf, 1/1 avg/max: 0.05/ 2.76 2501ms total, 20659 ops/sec
|
|
752
|
+
SET 4B buf, batch 50/1 avg/max: 0.25/ 1.76 2501ms total, 194962 ops/sec
|
|
753
|
+
GET 4B str, 1/1 avg/max: 0.02/ 1.55 2501ms total, 45156 ops/sec
|
|
754
|
+
GET 4B str, batch 50/1 avg/max: 0.09/ 3.15 2501ms total, 524110 ops/sec
|
|
755
|
+
GET 4B buf, 1/1 avg/max: 0.02/ 3.07 2501ms total, 44563 ops/sec
|
|
756
|
+
GET 4B buf, batch 50/1 avg/max: 0.10/ 3.18 2501ms total, 473171 ops/sec
|
|
757
|
+
SET 4KiB str, 1/1 avg/max: 0.03/ 1.54 2501ms total, 32627 ops/sec
|
|
758
|
+
SET 4KiB str, batch 50/1 avg/max: 0.34/ 1.89 2501ms total, 146861 ops/sec
|
|
759
|
+
SET 4KiB buf, 1/1 avg/max: 0.05/ 2.85 2501ms total, 20688 ops/sec
|
|
760
|
+
SET 4KiB buf, batch 50/1 avg/max: 0.36/ 1.83 2501ms total, 138165 ops/sec
|
|
761
|
+
GET 4KiB str, 1/1 avg/max: 0.02/ 1.37 2501ms total, 39389 ops/sec
|
|
762
|
+
GET 4KiB str, batch 50/1 avg/max: 0.24/ 1.81 2501ms total, 208157 ops/sec
|
|
763
|
+
GET 4KiB buf, 1/1 avg/max: 0.02/ 2.63 2501ms total, 39918 ops/sec
|
|
764
|
+
GET 4KiB buf, batch 50/1 avg/max: 0.31/ 8.56 2501ms total, 161575 ops/sec
|
|
765
|
+
INCR, 1/1 avg/max: 0.02/ 4.69 2501ms total, 45685 ops/sec
|
|
766
|
+
INCR, batch 50/1 avg/max: 0.09/ 3.06 2501ms total, 539964 ops/sec
|
|
767
|
+
LPUSH, 1/1 avg/max: 0.02/ 3.04 2501ms total, 41253 ops/sec
|
|
768
|
+
LPUSH, batch 50/1 avg/max: 0.12/ 1.94 2501ms total, 425090 ops/sec
|
|
769
|
+
LRANGE 10, 1/1 avg/max: 0.02/ 2.28 2501ms total, 39850 ops/sec
|
|
770
|
+
LRANGE 10, batch 50/1 avg/max: 0.25/ 1.85 2501ms total, 194302 ops/sec
|
|
771
|
+
LRANGE 100, 1/1 avg/max: 0.05/ 2.93 2501ms total, 21026 ops/sec
|
|
772
|
+
LRANGE 100, batch 50/1 avg/max: 1.52/ 2.89 2501ms total, 32767 ops/sec
|
|
773
|
+
SET 4MiB str, 1/1 avg/max: 5.16/ 15.55 2502ms total, 193 ops/sec
|
|
774
|
+
SET 4MiB str, batch 20/1 avg/max: 89.73/ 99.96 2513ms total, 223 ops/sec
|
|
775
|
+
SET 4MiB buf, 1/1 avg/max: 2.23/ 8.35 2501ms total, 446 ops/sec
|
|
776
|
+
SET 4MiB buf, batch 20/1 avg/max: 41.47/ 50.91 2530ms total, 482 ops/sec
|
|
777
|
+
GET 4MiB str, 1/1 avg/max: 2.79/ 10.91 2502ms total, 358 ops/sec
|
|
778
|
+
GET 4MiB str, batch 20/1 avg/max: 101.61/118.11 2541ms total, 197 ops/sec
|
|
779
|
+
GET 4MiB buf, 1/1 avg/max: 2.32/ 14.93 2502ms total, 430 ops/sec
|
|
780
|
+
GET 4MiB buf, batch 20/1 avg/max: 65.01/ 84.72 2536ms total, 308 ops/sec
|
|
758
781
|
```
|
|
759
782
|
|
|
760
|
-
The hiredis and js parser should most of the time be on the same level. But if you use Redis for big SUNION/SINTER/LRANGE/ZRANGE hiredis is faster.
|
|
761
|
-
Therefor the hiredis parser is the default used in node_redis. To use `hiredis`, do:
|
|
762
|
-
|
|
763
|
-
npm install hiredis redis
|
|
764
|
-
|
|
765
783
|
## Debugging
|
|
766
784
|
|
|
767
785
|
To get debug output run your `node_redis` application with `NODE_DEBUG=redis`.
|
|
768
786
|
|
|
787
|
+
This is also going to result in good stack traces opposed to useless ones otherwise for any async operation.
|
|
788
|
+
If you only want to have good stack traces but not the debug output run your application in development mode instead (`NODE_ENV=development`).
|
|
789
|
+
|
|
790
|
+
Good stack traces are only activated in development and debug mode as this results in a significant performance penalty.
|
|
791
|
+
|
|
792
|
+
___Comparison___:
|
|
793
|
+
Useless stack trace:
|
|
794
|
+
```
|
|
795
|
+
ReplyError: ERR wrong number of arguments for 'set' command
|
|
796
|
+
at parseError (/home/ruben/repos/redis/node_modules/redis-parser/lib/parser.js:158:12)
|
|
797
|
+
at parseType (/home/ruben/repos/redis/node_modules/redis-parser/lib/parser.js:219:14)
|
|
798
|
+
```
|
|
799
|
+
Good stack trace:
|
|
800
|
+
```
|
|
801
|
+
ReplyError: ERR wrong number of arguments for 'set' command
|
|
802
|
+
at new Command (/home/ruben/repos/redis/lib/command.js:9:902)
|
|
803
|
+
at RedisClient.set (/home/ruben/repos/redis/lib/commands.js:9:3238)
|
|
804
|
+
at Context.<anonymous> (/home/ruben/repos/redis/test/good_stacks.spec.js:20:20)
|
|
805
|
+
at callFnAsync (/home/ruben/repos/redis/node_modules/mocha/lib/runnable.js:349:8)
|
|
806
|
+
at Test.Runnable.run (/home/ruben/repos/redis/node_modules/mocha/lib/runnable.js:301:7)
|
|
807
|
+
at Runner.runTest (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:422:10)
|
|
808
|
+
at /home/ruben/repos/redis/node_modules/mocha/lib/runner.js:528:12
|
|
809
|
+
at next (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:342:14)
|
|
810
|
+
at /home/ruben/repos/redis/node_modules/mocha/lib/runner.js:352:7
|
|
811
|
+
at next (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:284:14)
|
|
812
|
+
at Immediate._onImmediate (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:320:5)
|
|
813
|
+
at processImmediate [as _immediateCallback] (timers.js:383:17)
|
|
814
|
+
```
|
|
815
|
+
|
|
769
816
|
## How to Contribute
|
|
770
817
|
- Open a pull request or an issue about what you want to implement / change. We're glad for any help!
|
|
771
818
|
- Please be aware that we'll only accept fully tested code.
|
package/changelog.md
CHANGED
|
@@ -1,18 +1,56 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
## v.2.6.3 - 31 Oct, 2016
|
|
5
|
+
|
|
6
|
+
Bugfixes
|
|
7
|
+
|
|
8
|
+
- Do not change the tls setting to camel_case
|
|
9
|
+
- Fix domain handling in combination with the offline queue (2.5.3 regression)
|
|
10
|
+
|
|
11
|
+
## v.2.6.2 - 16 Jun, 2016
|
|
12
|
+
|
|
13
|
+
Bugfixes
|
|
14
|
+
|
|
15
|
+
- Fixed individual callbacks of a transaction not being called (2.6.0 regression)
|
|
16
|
+
|
|
17
|
+
## v.2.6.1 - 02 Jun, 2016
|
|
18
|
+
|
|
19
|
+
Bugfixes
|
|
20
|
+
|
|
21
|
+
- Fixed invalid function name being exported
|
|
22
|
+
|
|
23
|
+
## v.2.6.0 - 01 Jun, 2016
|
|
24
|
+
|
|
25
|
+
In addition to the pre-releases the following changes exist in v.2.6.0:
|
|
26
|
+
|
|
27
|
+
Features
|
|
28
|
+
|
|
29
|
+
- Updated [redis-parser](https://github.com/NodeRedis/node-redis-parser) dependency ([changelog](https://github.com/NodeRedis/node-redis-parser/releases/tag/v.2.0.0))
|
|
30
|
+
- The JS parser is from now on the new default as it is a lot faster than the hiredis parser
|
|
31
|
+
- This is no BC as there is no changed behavior for the user at all but just a performance improvement. Explicitly requireing the Hiredis parser is still possible.
|
|
32
|
+
- Added name property to all Redis functions (Node.js >= 4.0)
|
|
33
|
+
- Improved stack traces in development and debug mode
|
|
34
|
+
|
|
35
|
+
Bugfixes
|
|
36
|
+
|
|
37
|
+
- Reverted support for `__proto__` (v.2.6.0-2) to prevent and breaking change
|
|
38
|
+
|
|
39
|
+
Deprecations
|
|
40
|
+
|
|
41
|
+
- The `parser` option is deprecated and should be removed. The built-in Javascript parser is a lot faster than the hiredis parser and has more features
|
|
42
|
+
|
|
4
43
|
## v.2.6.0-2 - 29 Apr, 2016
|
|
5
44
|
|
|
6
45
|
Features
|
|
7
46
|
|
|
8
|
-
- Added support for the new
|
|
47
|
+
- Added support for the new [CLIENT REPLY ON|OFF|SKIP](http://redis.io/commands/client-reply) command (Redis v.3.2)
|
|
9
48
|
- Added support for camelCase
|
|
10
49
|
- The Node.js landscape default is to use camelCase. node_redis is a bit out of the box here
|
|
11
50
|
but from now on it is possible to use both, just as you prefer!
|
|
12
51
|
- If there's any documented variable missing as camelCased, please open a issue for it
|
|
13
52
|
- Improve error handling significantly
|
|
14
53
|
- Only emit an error if the error has not already been handled in a callback
|
|
15
|
-
- Emit an error if a command would otherwise silently fail (no callback present)
|
|
16
54
|
- Improved unspecific error messages e.g. "Connection gone from end / close event"
|
|
17
55
|
- Added `args` to command errors to improve identification of the error
|
|
18
56
|
- Added origin to errors if there's e.g. a connection error
|
package/index.js
CHANGED
|
@@ -4,10 +4,9 @@ var net = require('net');
|
|
|
4
4
|
var tls = require('tls');
|
|
5
5
|
var util = require('util');
|
|
6
6
|
var utils = require('./lib/utils');
|
|
7
|
+
var Command = require('./lib/command');
|
|
7
8
|
var Queue = require('double-ended-queue');
|
|
8
9
|
var errorClasses = require('./lib/customErrors');
|
|
9
|
-
var Command = require('./lib/command').Command;
|
|
10
|
-
var OfflineCommand = require('./lib/command').OfflineCommand;
|
|
11
10
|
var EventEmitter = require('events');
|
|
12
11
|
var Parser = require('redis-parser');
|
|
13
12
|
var commands = require('redis-commands');
|
|
@@ -154,11 +153,11 @@ function RedisClient (options, stream) {
|
|
|
154
153
|
this.pipeline = false;
|
|
155
154
|
this.sub_commands_left = 0;
|
|
156
155
|
this.times_connected = 0;
|
|
157
|
-
this.options = options;
|
|
158
156
|
this.buffers = options.return_buffers || options.detect_buffers;
|
|
157
|
+
this.options = options;
|
|
159
158
|
this.reply = 'ON'; // Returning replies is the default
|
|
160
159
|
// Init parser
|
|
161
|
-
this.reply_parser = create_parser(this
|
|
160
|
+
this.reply_parser = create_parser(this);
|
|
162
161
|
this.create_stream();
|
|
163
162
|
// The listeners will not be attached right away, so let's print the deprecation message while the listener is attached
|
|
164
163
|
this.on('newListener', function (event) {
|
|
@@ -184,18 +183,17 @@ util.inherits(RedisClient, EventEmitter);
|
|
|
184
183
|
RedisClient.connection_id = 0;
|
|
185
184
|
|
|
186
185
|
function create_parser (self) {
|
|
187
|
-
return Parser({
|
|
186
|
+
return new Parser({
|
|
188
187
|
returnReply: function (data) {
|
|
189
188
|
self.return_reply(data);
|
|
190
189
|
},
|
|
191
190
|
returnError: function (err) {
|
|
192
191
|
// Return a ReplyError to indicate Redis returned an error
|
|
193
|
-
self.return_error(
|
|
192
|
+
self.return_error(err);
|
|
194
193
|
},
|
|
195
194
|
returnFatalError: function (err) {
|
|
196
195
|
// Error out all fired commands. Otherwise they might rely on faulty data. We have to reconnect to get in a working state again
|
|
197
196
|
// Note: the execution order is important. First flush and emit, then create the stream
|
|
198
|
-
err = new errorClasses.ReplyError(err);
|
|
199
197
|
err.message += '. Please report this.';
|
|
200
198
|
self.ready = false;
|
|
201
199
|
self.flush_and_error({
|
|
@@ -209,8 +207,8 @@ function create_parser (self) {
|
|
|
209
207
|
self.create_stream();
|
|
210
208
|
},
|
|
211
209
|
returnBuffers: self.buffers || self.message_buffers,
|
|
212
|
-
name: self.options.parser,
|
|
213
|
-
stringNumbers: self.options.string_numbers
|
|
210
|
+
name: self.options.parser || 'javascript',
|
|
211
|
+
stringNumbers: self.options.string_numbers || false
|
|
214
212
|
});
|
|
215
213
|
}
|
|
216
214
|
|
|
@@ -350,6 +348,9 @@ RedisClient.prototype.flush_and_error = function (error_attributes, options) {
|
|
|
350
348
|
// Don't flush everything from the queue
|
|
351
349
|
for (var command_obj = this[queue_names[i]].shift(); command_obj; command_obj = this[queue_names[i]].shift()) {
|
|
352
350
|
var err = new errorClasses.AbortError(error_attributes);
|
|
351
|
+
if (command_obj.error) {
|
|
352
|
+
err.stack = err.stack + command_obj.error.stack.replace(/^Error.*?\n/, '\n');
|
|
353
|
+
}
|
|
353
354
|
err.command = command_obj.command.toUpperCase();
|
|
354
355
|
if (command_obj.args && command_obj.args.length) {
|
|
355
356
|
err.args = command_obj.args;
|
|
@@ -444,14 +445,10 @@ RedisClient.prototype.on_ready = function () {
|
|
|
444
445
|
|
|
445
446
|
// Restore modal commands from previous connection. The order of the commands is important
|
|
446
447
|
if (this.selected_db !== undefined) {
|
|
447
|
-
this.internal_send_command('select', [this.selected_db]);
|
|
448
|
-
}
|
|
449
|
-
if (this.old_state !== null) {
|
|
450
|
-
this.monitoring = this.old_state.monitoring;
|
|
451
|
-
this.pub_sub_mode = this.old_state.pub_sub_mode;
|
|
448
|
+
this.internal_send_command(new Command('select', [this.selected_db]));
|
|
452
449
|
}
|
|
453
450
|
if (this.monitoring) { // Monitor has to be fired before pub sub commands
|
|
454
|
-
this.internal_send_command('monitor', []);
|
|
451
|
+
this.internal_send_command(new Command('monitor', []));
|
|
455
452
|
}
|
|
456
453
|
var callback_count = Object.keys(this.subscription_set).length;
|
|
457
454
|
if (!this.options.disable_resubscribing && callback_count) {
|
|
@@ -467,8 +464,8 @@ RedisClient.prototype.on_ready = function () {
|
|
|
467
464
|
debug('Sending pub/sub on_ready commands');
|
|
468
465
|
for (var key in this.subscription_set) {
|
|
469
466
|
var command = key.slice(0, key.indexOf('_'));
|
|
470
|
-
var args =
|
|
471
|
-
|
|
467
|
+
var args = this.subscription_set[key];
|
|
468
|
+
this[command]([args], callback);
|
|
472
469
|
}
|
|
473
470
|
this.send_offline_queue();
|
|
474
471
|
return;
|
|
@@ -531,7 +528,7 @@ RedisClient.prototype.ready_check = function () {
|
|
|
531
528
|
RedisClient.prototype.send_offline_queue = function () {
|
|
532
529
|
for (var command_obj = this.offline_queue.shift(); command_obj; command_obj = this.offline_queue.shift()) {
|
|
533
530
|
debug('Sending offline command: ' + command_obj.command);
|
|
534
|
-
this.internal_send_command(command_obj
|
|
531
|
+
this.internal_send_command(command_obj);
|
|
535
532
|
}
|
|
536
533
|
this.drain();
|
|
537
534
|
};
|
|
@@ -574,13 +571,6 @@ RedisClient.prototype.connection_gone = function (why, error) {
|
|
|
574
571
|
this.cork = noop;
|
|
575
572
|
this.uncork = noop;
|
|
576
573
|
this.pipeline = false;
|
|
577
|
-
|
|
578
|
-
var state = {
|
|
579
|
-
monitoring: this.monitoring,
|
|
580
|
-
pub_sub_mode: this.pub_sub_mode
|
|
581
|
-
};
|
|
582
|
-
this.old_state = state;
|
|
583
|
-
this.monitoring = false;
|
|
584
574
|
this.pub_sub_mode = 0;
|
|
585
575
|
|
|
586
576
|
// since we are collapsing end and close, users don't expect to be called twice
|
|
@@ -682,6 +672,9 @@ RedisClient.prototype.connection_gone = function (why, error) {
|
|
|
682
672
|
|
|
683
673
|
RedisClient.prototype.return_error = function (err) {
|
|
684
674
|
var command_obj = this.command_queue.shift();
|
|
675
|
+
if (command_obj.error) {
|
|
676
|
+
err.stack = command_obj.error.stack.replace(/^Error.*?\n/, 'ReplyError: ' + err.message + '\n');
|
|
677
|
+
}
|
|
685
678
|
err.command = command_obj.command.toUpperCase();
|
|
686
679
|
if (command_obj.args && command_obj.args.length) {
|
|
687
680
|
err.args = command_obj.args;
|
|
@@ -835,7 +828,6 @@ RedisClient.prototype.return_reply = function (reply) {
|
|
|
835
828
|
|
|
836
829
|
function handle_offline_command (self, command_obj) {
|
|
837
830
|
var command = command_obj.command;
|
|
838
|
-
var callback = command_obj.callback;
|
|
839
831
|
var err, msg;
|
|
840
832
|
if (self.closing || !self.enable_offline_queue) {
|
|
841
833
|
command = command.toUpperCase();
|
|
@@ -853,10 +845,10 @@ function handle_offline_command (self, command_obj) {
|
|
|
853
845
|
code: 'NR_CLOSED',
|
|
854
846
|
command: command
|
|
855
847
|
});
|
|
856
|
-
if (command_obj.args
|
|
848
|
+
if (command_obj.args.length) {
|
|
857
849
|
err.args = command_obj.args;
|
|
858
850
|
}
|
|
859
|
-
utils.reply_in_order(self, callback, err);
|
|
851
|
+
utils.reply_in_order(self, command_obj.callback, err);
|
|
860
852
|
} else {
|
|
861
853
|
debug('Queueing ' + command + ' for next server connection.');
|
|
862
854
|
self.offline_queue.push(command_obj);
|
|
@@ -866,22 +858,23 @@ function handle_offline_command (self, command_obj) {
|
|
|
866
858
|
|
|
867
859
|
// Do not call internal_send_command directly, if you are not absolutly certain it handles everything properly
|
|
868
860
|
// e.g. monitor / info does not work with internal_send_command only
|
|
869
|
-
RedisClient.prototype.internal_send_command = function (
|
|
870
|
-
var arg, prefix_keys
|
|
861
|
+
RedisClient.prototype.internal_send_command = function (command_obj) {
|
|
862
|
+
var arg, prefix_keys;
|
|
871
863
|
var i = 0;
|
|
872
864
|
var command_str = '';
|
|
865
|
+
var args = command_obj.args;
|
|
866
|
+
var command = command_obj.command;
|
|
873
867
|
var len = args.length;
|
|
874
868
|
var big_data = false;
|
|
875
|
-
var buffer_args = false;
|
|
876
869
|
var args_copy = new Array(len);
|
|
877
870
|
|
|
878
|
-
if (process.domain && callback) {
|
|
879
|
-
callback = process.domain.bind(callback);
|
|
871
|
+
if (process.domain && command_obj.callback) {
|
|
872
|
+
command_obj.callback = process.domain.bind(command_obj.callback);
|
|
880
873
|
}
|
|
881
874
|
|
|
882
875
|
if (this.ready === false || this.stream.writable === false) {
|
|
883
876
|
// Handle offline commands right away
|
|
884
|
-
handle_offline_command(this,
|
|
877
|
+
handle_offline_command(this, command_obj);
|
|
885
878
|
return false; // Indicate buffering
|
|
886
879
|
}
|
|
887
880
|
|
|
@@ -906,7 +899,7 @@ RedisClient.prototype.internal_send_command = function (command, args, callback,
|
|
|
906
899
|
args_copy[i] = 'null'; // Backwards compatible :/
|
|
907
900
|
} else if (Buffer.isBuffer(args[i])) {
|
|
908
901
|
args_copy[i] = args[i];
|
|
909
|
-
buffer_args = true;
|
|
902
|
+
command_obj.buffer_args = true;
|
|
910
903
|
big_data = true;
|
|
911
904
|
} else {
|
|
912
905
|
this.warn(
|
|
@@ -928,8 +921,6 @@ RedisClient.prototype.internal_send_command = function (command, args, callback,
|
|
|
928
921
|
args_copy[i] = '' + args[i];
|
|
929
922
|
}
|
|
930
923
|
}
|
|
931
|
-
// Pass the original args to make sure in error cases the original arguments are returned
|
|
932
|
-
command_obj = new Command(command, args, buffer_args, callback);
|
|
933
924
|
|
|
934
925
|
if (this.options.prefix) {
|
|
935
926
|
prefix_keys = commands.getKeyIndexes(command, args_copy);
|
|
@@ -968,8 +959,8 @@ RedisClient.prototype.internal_send_command = function (command, args, callback,
|
|
|
968
959
|
debug('send_command: buffer send ' + arg.length + ' bytes');
|
|
969
960
|
}
|
|
970
961
|
}
|
|
971
|
-
if (call_on_write) {
|
|
972
|
-
call_on_write();
|
|
962
|
+
if (command_obj.call_on_write) {
|
|
963
|
+
command_obj.call_on_write();
|
|
973
964
|
}
|
|
974
965
|
// Handle `CLIENT REPLY ON|OFF|SKIP`
|
|
975
966
|
// This has to be checked after call_on_write
|
|
@@ -979,8 +970,8 @@ RedisClient.prototype.internal_send_command = function (command, args, callback,
|
|
|
979
970
|
} else {
|
|
980
971
|
// Do not expect a reply
|
|
981
972
|
// Does this work in combination with the pub sub mode?
|
|
982
|
-
if (callback) {
|
|
983
|
-
utils.reply_in_order(this, callback, null, undefined, this.command_queue);
|
|
973
|
+
if (command_obj.callback) {
|
|
974
|
+
utils.reply_in_order(this, command_obj.callback, null, undefined, this.command_queue);
|
|
984
975
|
}
|
|
985
976
|
if (this.reply === 'SKIP') {
|
|
986
977
|
this.reply = 'SKIP_ONE_MORE';
|
|
@@ -1043,7 +1034,7 @@ Object.defineProperty(RedisClient.prototype, 'offline_queue_length', {
|
|
|
1043
1034
|
});
|
|
1044
1035
|
|
|
1045
1036
|
// Add support for camelCase by adding read only properties to the client
|
|
1046
|
-
// All known exposed
|
|
1037
|
+
// All known exposed snake_case variables are added here
|
|
1047
1038
|
Object.defineProperty(RedisClient.prototype, 'retryDelay', {
|
|
1048
1039
|
get: function () {
|
|
1049
1040
|
return this.retry_delay;
|
|
@@ -1093,7 +1084,7 @@ exports.RedisClient = RedisClient;
|
|
|
1093
1084
|
exports.print = utils.print;
|
|
1094
1085
|
exports.Multi = require('./lib/multi');
|
|
1095
1086
|
exports.AbortError = errorClasses.AbortError;
|
|
1096
|
-
exports.ReplyError =
|
|
1087
|
+
exports.ReplyError = Parser.ReplyError;
|
|
1097
1088
|
exports.AggregateError = errorClasses.AggregateError;
|
|
1098
1089
|
|
|
1099
1090
|
// Add all redis commands / node_redis api to the client
|
package/lib/command.js
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
// a named constructor helps it show up meaningfully in the V8 CPU profiler and in heap snapshots.
|
|
5
|
-
function Command (command, args, buffer_args, callback) {
|
|
6
|
-
this.command = command;
|
|
7
|
-
this.args = args;
|
|
8
|
-
this.buffer_args = buffer_args;
|
|
9
|
-
this.callback = callback;
|
|
10
|
-
}
|
|
3
|
+
var betterStackTraces = /development/i.test(process.env.NODE_ENV) || /\bredis\b/i.test(process.env.NODE_DEBUG);
|
|
11
4
|
|
|
12
|
-
function
|
|
5
|
+
function Command (command, args, callback, call_on_write) {
|
|
13
6
|
this.command = command;
|
|
14
7
|
this.args = args;
|
|
8
|
+
this.buffer_args = false;
|
|
15
9
|
this.callback = callback;
|
|
16
10
|
this.call_on_write = call_on_write;
|
|
11
|
+
if (betterStackTraces) {
|
|
12
|
+
this.error = new Error();
|
|
13
|
+
}
|
|
17
14
|
}
|
|
18
15
|
|
|
19
|
-
module.exports =
|
|
20
|
-
Command: Command,
|
|
21
|
-
OfflineCommand: OfflineCommand
|
|
22
|
-
};
|
|
16
|
+
module.exports = Command;
|
package/lib/commands.js
CHANGED
|
@@ -3,12 +3,29 @@
|
|
|
3
3
|
var commands = require('redis-commands');
|
|
4
4
|
var Multi = require('./multi');
|
|
5
5
|
var RedisClient = require('../').RedisClient;
|
|
6
|
+
var Command = require('./command');
|
|
7
|
+
// Feature detect if a function may change it's name
|
|
8
|
+
var changeFunctionName = (function () {
|
|
9
|
+
var fn = function abc () {};
|
|
10
|
+
try {
|
|
11
|
+
Object.defineProperty(fn, 'name', {
|
|
12
|
+
value: 'foobar'
|
|
13
|
+
});
|
|
14
|
+
return true;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}());
|
|
6
19
|
|
|
7
20
|
// TODO: Rewrite this including the invidual commands into a Commands class
|
|
8
21
|
// that provided a functionality to add new commands to the client
|
|
9
22
|
|
|
10
23
|
commands.list.forEach(function (command) {
|
|
11
24
|
|
|
25
|
+
// Some rare Redis commands use special characters in their command name
|
|
26
|
+
// Convert those to a underscore to prevent using invalid function names
|
|
27
|
+
var commandName = command.replace(/(?:^([0-9])|[^a-zA-Z0-9_$])/g, '_$1');
|
|
28
|
+
|
|
12
29
|
// Do not override existing functions
|
|
13
30
|
if (!RedisClient.prototype[command]) {
|
|
14
31
|
RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function () {
|
|
@@ -42,8 +59,13 @@ commands.list.forEach(function (command) {
|
|
|
42
59
|
arr[i] = arguments[i];
|
|
43
60
|
}
|
|
44
61
|
}
|
|
45
|
-
return this.internal_send_command(command, arr, callback);
|
|
62
|
+
return this.internal_send_command(new Command(command, arr, callback));
|
|
46
63
|
};
|
|
64
|
+
if (changeFunctionName) {
|
|
65
|
+
Object.defineProperty(RedisClient.prototype[command], 'name', {
|
|
66
|
+
value: commandName
|
|
67
|
+
});
|
|
68
|
+
}
|
|
47
69
|
}
|
|
48
70
|
|
|
49
71
|
// Do not override existing functions
|
|
@@ -79,8 +101,13 @@ commands.list.forEach(function (command) {
|
|
|
79
101
|
arr[i] = arguments[i];
|
|
80
102
|
}
|
|
81
103
|
}
|
|
82
|
-
this.queue.push(
|
|
104
|
+
this.queue.push(new Command(command, arr, callback));
|
|
83
105
|
return this;
|
|
84
106
|
};
|
|
107
|
+
if (changeFunctionName) {
|
|
108
|
+
Object.defineProperty(Multi.prototype[command], 'name', {
|
|
109
|
+
value: commandName
|
|
110
|
+
});
|
|
111
|
+
}
|
|
85
112
|
}
|
|
86
113
|
});
|
package/lib/customErrors.js
CHANGED
|
@@ -4,75 +4,43 @@ var util = require('util');
|
|
|
4
4
|
|
|
5
5
|
function AbortError (obj) {
|
|
6
6
|
Error.captureStackTrace(this, this.constructor);
|
|
7
|
-
var message;
|
|
8
|
-
Object.defineProperty(this, 'name', {
|
|
9
|
-
get: function () {
|
|
10
|
-
return this.constructor.name;
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
7
|
Object.defineProperty(this, 'message', {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
set: function (msg) {
|
|
18
|
-
message = msg;
|
|
19
|
-
}
|
|
8
|
+
value: obj.message || '',
|
|
9
|
+
configurable: true,
|
|
10
|
+
writable: true
|
|
20
11
|
});
|
|
21
12
|
for (var keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) {
|
|
22
13
|
this[key] = obj[key];
|
|
23
14
|
}
|
|
24
|
-
// Explicitly add the message
|
|
25
|
-
// If the obj is a error itself, the message is not enumerable
|
|
26
|
-
this.message = obj.message;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function ReplyError (obj) {
|
|
30
|
-
Error.captureStackTrace(this, this.constructor);
|
|
31
|
-
var tmp;
|
|
32
|
-
Object.defineProperty(this, 'name', {
|
|
33
|
-
get: function () {
|
|
34
|
-
return this.constructor.name;
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
Object.defineProperty(this, 'message', {
|
|
38
|
-
get: function () {
|
|
39
|
-
return tmp;
|
|
40
|
-
},
|
|
41
|
-
set: function (msg) {
|
|
42
|
-
tmp = msg;
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
this.message = obj.message;
|
|
46
15
|
}
|
|
47
16
|
|
|
48
17
|
function AggregateError (obj) {
|
|
49
18
|
Error.captureStackTrace(this, this.constructor);
|
|
50
|
-
var tmp;
|
|
51
|
-
Object.defineProperty(this, 'name', {
|
|
52
|
-
get: function () {
|
|
53
|
-
return this.constructor.name;
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
19
|
Object.defineProperty(this, 'message', {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
set: function (msg) {
|
|
61
|
-
tmp = msg;
|
|
62
|
-
}
|
|
20
|
+
value: obj.message || '',
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true
|
|
63
23
|
});
|
|
64
24
|
for (var keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) {
|
|
65
25
|
this[key] = obj[key];
|
|
66
26
|
}
|
|
67
|
-
this.message = obj.message;
|
|
68
27
|
}
|
|
69
28
|
|
|
70
|
-
util.inherits(ReplyError, Error);
|
|
71
29
|
util.inherits(AbortError, Error);
|
|
72
30
|
util.inherits(AggregateError, AbortError);
|
|
73
31
|
|
|
32
|
+
Object.defineProperty(AbortError.prototype, 'name', {
|
|
33
|
+
value: 'AbortError',
|
|
34
|
+
// configurable: true,
|
|
35
|
+
writable: true
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(AggregateError.prototype, 'name', {
|
|
38
|
+
value: 'AggregateError',
|
|
39
|
+
// configurable: true,
|
|
40
|
+
writable: true
|
|
41
|
+
});
|
|
42
|
+
|
|
74
43
|
module.exports = {
|
|
75
|
-
ReplyError: ReplyError,
|
|
76
44
|
AbortError: AbortError,
|
|
77
45
|
AggregateError: AggregateError
|
|
78
46
|
};
|
package/lib/extendedApi.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var utils = require('./utils');
|
|
4
4
|
var debug = require('./debug');
|
|
5
5
|
var RedisClient = require('../').RedisClient;
|
|
6
|
+
var Command = require('./command');
|
|
6
7
|
var noop = function () {};
|
|
7
8
|
|
|
8
9
|
/**********************************************
|
|
@@ -36,7 +37,7 @@ RedisClient.prototype.send_command = RedisClient.prototype.sendCommand = functio
|
|
|
36
37
|
// but this might change from time to time and at the moment there's no good way to distinguishe them
|
|
37
38
|
// from each other, so let's just do it do it this way for the time being
|
|
38
39
|
if (command === 'multi' || typeof this[command] !== 'function') {
|
|
39
|
-
return this.internal_send_command(command, args, callback);
|
|
40
|
+
return this.internal_send_command(new Command(command, args, callback));
|
|
40
41
|
}
|
|
41
42
|
if (typeof callback === 'function') {
|
|
42
43
|
args = args.concat([callback]); // Prevent manipulating the input array
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var utils = require('./utils');
|
|
4
4
|
var debug = require('./debug');
|
|
5
5
|
var Multi = require('./multi');
|
|
6
|
+
var Command = require('./command');
|
|
6
7
|
var no_password_is_set = /no password is set/;
|
|
7
8
|
var loading = /LOADING/;
|
|
8
9
|
var RedisClient = require('../').RedisClient;
|
|
@@ -42,33 +43,34 @@ function select_callback (self, db, callback) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
RedisClient.prototype.select = RedisClient.prototype.SELECT = function select (db, callback) {
|
|
45
|
-
return this.internal_send_command('select', [db], select_callback(this, db, callback));
|
|
46
|
+
return this.internal_send_command(new Command('select', [db], select_callback(this, db, callback)));
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
Multi.prototype.select = Multi.prototype.SELECT = function select (db, callback) {
|
|
49
|
-
this.queue.push(
|
|
50
|
+
this.queue.push(new Command('select', [db], select_callback(this._client, db, callback)));
|
|
50
51
|
return this;
|
|
51
52
|
};
|
|
52
53
|
|
|
53
|
-
function monitor_callback (self, callback) {
|
|
54
|
-
return function (err, res) {
|
|
55
|
-
if (err === null) {
|
|
56
|
-
self.monitoring = true;
|
|
57
|
-
}
|
|
58
|
-
utils.callback_or_emit(self, callback, err, res);
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
54
|
RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function monitor (callback) {
|
|
63
55
|
// Use a individual command, as this is a special case that does not has to be checked for any other command
|
|
64
|
-
|
|
56
|
+
var self = this;
|
|
57
|
+
var call_on_write = function () {
|
|
58
|
+
// Activating monitor mode has to happen before Redis returned the callback. The monitor result is returned first.
|
|
59
|
+
// Therefore we expect the command to be properly processed. If this is not the case, it's not an issue either.
|
|
60
|
+
self.monitoring = true;
|
|
61
|
+
};
|
|
62
|
+
return this.internal_send_command(new Command('monitor', [], callback, call_on_write));
|
|
65
63
|
};
|
|
66
64
|
|
|
67
65
|
// Only works with batch, not in a transaction
|
|
68
66
|
Multi.prototype.monitor = Multi.prototype.MONITOR = function monitor (callback) {
|
|
69
67
|
// Use a individual command, as this is a special case that does not has to be checked for any other command
|
|
70
68
|
if (this.exec !== this.exec_transaction) {
|
|
71
|
-
|
|
69
|
+
var self = this;
|
|
70
|
+
var call_on_write = function () {
|
|
71
|
+
self._client.monitoring = true;
|
|
72
|
+
};
|
|
73
|
+
this.queue.push(new Command('monitor', [], callback, call_on_write));
|
|
72
74
|
return this;
|
|
73
75
|
}
|
|
74
76
|
// Set multi monitoring to indicate the exec that it should abort
|
|
@@ -96,11 +98,11 @@ function quit_callback (self, callback) {
|
|
|
96
98
|
};
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
RedisClient.prototype.QUIT = RedisClient.prototype.quit = function (callback) {
|
|
101
|
+
RedisClient.prototype.QUIT = RedisClient.prototype.quit = function quit (callback) {
|
|
100
102
|
// TODO: Consider this for v.3
|
|
101
103
|
// Allow the quit command to be fired as soon as possible to prevent it landing in the offline queue.
|
|
102
104
|
// this.ready = this.offline_queue.length === 0;
|
|
103
|
-
var backpressure_indicator = this.internal_send_command('quit', [], quit_callback(this, callback));
|
|
105
|
+
var backpressure_indicator = this.internal_send_command(new Command('quit', [], quit_callback(this, callback)));
|
|
104
106
|
// Calling quit should always end the connection, no matter if there's a connection or not
|
|
105
107
|
this.closing = true;
|
|
106
108
|
this.ready = false;
|
|
@@ -108,14 +110,14 @@ RedisClient.prototype.QUIT = RedisClient.prototype.quit = function (callback) {
|
|
|
108
110
|
};
|
|
109
111
|
|
|
110
112
|
// Only works with batch, not in a transaction
|
|
111
|
-
Multi.prototype.QUIT = Multi.prototype.quit = function (callback) {
|
|
113
|
+
Multi.prototype.QUIT = Multi.prototype.quit = function quit (callback) {
|
|
112
114
|
var self = this._client;
|
|
113
115
|
var call_on_write = function () {
|
|
114
116
|
// If called in a multi context, we expect redis is available
|
|
115
117
|
self.closing = true;
|
|
116
118
|
self.ready = false;
|
|
117
119
|
};
|
|
118
|
-
this.queue.push(
|
|
120
|
+
this.queue.push(new Command('quit', [], quit_callback(self, callback), call_on_write));
|
|
119
121
|
return this;
|
|
120
122
|
};
|
|
121
123
|
|
|
@@ -164,7 +166,7 @@ RedisClient.prototype.info = RedisClient.prototype.INFO = function info (section
|
|
|
164
166
|
} else if (section !== undefined) {
|
|
165
167
|
args = Array.isArray(section) ? section : [section];
|
|
166
168
|
}
|
|
167
|
-
return this.internal_send_command('info', args, info_callback(this, callback));
|
|
169
|
+
return this.internal_send_command(new Command('info', args, info_callback(this, callback)));
|
|
168
170
|
};
|
|
169
171
|
|
|
170
172
|
Multi.prototype.info = Multi.prototype.INFO = function info (section, callback) {
|
|
@@ -174,7 +176,7 @@ Multi.prototype.info = Multi.prototype.INFO = function info (section, callback)
|
|
|
174
176
|
} else if (section !== undefined) {
|
|
175
177
|
args = Array.isArray(section) ? section : [section];
|
|
176
178
|
}
|
|
177
|
-
this.queue.push(
|
|
179
|
+
this.queue.push(new Command('info', args, info_callback(this._client, callback)));
|
|
178
180
|
return this;
|
|
179
181
|
};
|
|
180
182
|
|
|
@@ -205,7 +207,7 @@ RedisClient.prototype.auth = RedisClient.prototype.AUTH = function auth (pass, c
|
|
|
205
207
|
this.auth_pass = pass;
|
|
206
208
|
var ready = this.ready;
|
|
207
209
|
this.ready = ready || this.offline_queue.length === 0;
|
|
208
|
-
var tmp = this.internal_send_command('auth', [pass], auth_callback(this, pass, callback));
|
|
210
|
+
var tmp = this.internal_send_command(new Command('auth', [pass], auth_callback(this, pass, callback)));
|
|
209
211
|
this.ready = ready;
|
|
210
212
|
return tmp;
|
|
211
213
|
};
|
|
@@ -216,7 +218,7 @@ Multi.prototype.auth = Multi.prototype.AUTH = function auth (pass, callback) {
|
|
|
216
218
|
|
|
217
219
|
// Stash auth for connect and reconnect.
|
|
218
220
|
this.auth_pass = pass;
|
|
219
|
-
this.queue.push(
|
|
221
|
+
this.queue.push(new Command('auth', [pass], auth_callback(this._client, callback)));
|
|
220
222
|
return this;
|
|
221
223
|
};
|
|
222
224
|
|
|
@@ -262,7 +264,7 @@ RedisClient.prototype.client = RedisClient.prototype.CLIENT = function client ()
|
|
|
262
264
|
};
|
|
263
265
|
}
|
|
264
266
|
}
|
|
265
|
-
return this.internal_send_command('client', arr, callback, call_on_write);
|
|
267
|
+
return this.internal_send_command(new Command('client', arr, callback, call_on_write));
|
|
266
268
|
};
|
|
267
269
|
|
|
268
270
|
Multi.prototype.client = Multi.prototype.CLIENT = function client () {
|
|
@@ -307,7 +309,7 @@ Multi.prototype.client = Multi.prototype.CLIENT = function client () {
|
|
|
307
309
|
};
|
|
308
310
|
}
|
|
309
311
|
}
|
|
310
|
-
this.queue.push(
|
|
312
|
+
this.queue.push(new Command('client', arr, callback, call_on_write));
|
|
311
313
|
return this;
|
|
312
314
|
};
|
|
313
315
|
|
|
@@ -347,7 +349,7 @@ RedisClient.prototype.hmset = RedisClient.prototype.HMSET = function hmset () {
|
|
|
347
349
|
arr[i] = arguments[i];
|
|
348
350
|
}
|
|
349
351
|
}
|
|
350
|
-
return this.internal_send_command('hmset', arr, callback);
|
|
352
|
+
return this.internal_send_command(new Command('hmset', arr, callback));
|
|
351
353
|
};
|
|
352
354
|
|
|
353
355
|
Multi.prototype.hmset = Multi.prototype.HMSET = function hmset () {
|
|
@@ -386,7 +388,7 @@ Multi.prototype.hmset = Multi.prototype.HMSET = function hmset () {
|
|
|
386
388
|
arr[i] = arguments[i];
|
|
387
389
|
}
|
|
388
390
|
}
|
|
389
|
-
this.queue.push(
|
|
391
|
+
this.queue.push(new Command('hmset', arr, callback));
|
|
390
392
|
return this;
|
|
391
393
|
};
|
|
392
394
|
|
|
@@ -414,7 +416,7 @@ RedisClient.prototype.subscribe = RedisClient.prototype.SUBSCRIBE = function sub
|
|
|
414
416
|
var call_on_write = function () {
|
|
415
417
|
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
|
|
416
418
|
};
|
|
417
|
-
return this.internal_send_command('subscribe', arr, callback, call_on_write);
|
|
419
|
+
return this.internal_send_command(new Command('subscribe', arr, callback, call_on_write));
|
|
418
420
|
};
|
|
419
421
|
|
|
420
422
|
Multi.prototype.subscribe = Multi.prototype.SUBSCRIBE = function subscribe () {
|
|
@@ -441,7 +443,7 @@ Multi.prototype.subscribe = Multi.prototype.SUBSCRIBE = function subscribe () {
|
|
|
441
443
|
var call_on_write = function () {
|
|
442
444
|
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
|
|
443
445
|
};
|
|
444
|
-
this.queue.push(
|
|
446
|
+
this.queue.push(new Command('subscribe', arr, callback, call_on_write));
|
|
445
447
|
return this;
|
|
446
448
|
};
|
|
447
449
|
|
|
@@ -470,7 +472,7 @@ RedisClient.prototype.unsubscribe = RedisClient.prototype.UNSUBSCRIBE = function
|
|
|
470
472
|
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
|
|
471
473
|
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
|
|
472
474
|
};
|
|
473
|
-
return this.internal_send_command('unsubscribe', arr, callback, call_on_write);
|
|
475
|
+
return this.internal_send_command(new Command('unsubscribe', arr, callback, call_on_write));
|
|
474
476
|
};
|
|
475
477
|
|
|
476
478
|
Multi.prototype.unsubscribe = Multi.prototype.UNSUBSCRIBE = function unsubscribe () {
|
|
@@ -498,7 +500,7 @@ Multi.prototype.unsubscribe = Multi.prototype.UNSUBSCRIBE = function unsubscribe
|
|
|
498
500
|
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
|
|
499
501
|
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
|
|
500
502
|
};
|
|
501
|
-
this.queue.push(
|
|
503
|
+
this.queue.push(new Command('unsubscribe', arr, callback, call_on_write));
|
|
502
504
|
return this;
|
|
503
505
|
};
|
|
504
506
|
|
|
@@ -526,7 +528,7 @@ RedisClient.prototype.psubscribe = RedisClient.prototype.PSUBSCRIBE = function p
|
|
|
526
528
|
var call_on_write = function () {
|
|
527
529
|
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
|
|
528
530
|
};
|
|
529
|
-
return this.internal_send_command('psubscribe', arr, callback, call_on_write);
|
|
531
|
+
return this.internal_send_command(new Command('psubscribe', arr, callback, call_on_write));
|
|
530
532
|
};
|
|
531
533
|
|
|
532
534
|
Multi.prototype.psubscribe = Multi.prototype.PSUBSCRIBE = function psubscribe () {
|
|
@@ -553,7 +555,7 @@ Multi.prototype.psubscribe = Multi.prototype.PSUBSCRIBE = function psubscribe ()
|
|
|
553
555
|
var call_on_write = function () {
|
|
554
556
|
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
|
|
555
557
|
};
|
|
556
|
-
this.queue.push(
|
|
558
|
+
this.queue.push(new Command('psubscribe', arr, callback, call_on_write));
|
|
557
559
|
return this;
|
|
558
560
|
};
|
|
559
561
|
|
|
@@ -582,7 +584,7 @@ RedisClient.prototype.punsubscribe = RedisClient.prototype.PUNSUBSCRIBE = functi
|
|
|
582
584
|
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
|
|
583
585
|
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
|
|
584
586
|
};
|
|
585
|
-
return this.internal_send_command('punsubscribe', arr, callback, call_on_write);
|
|
587
|
+
return this.internal_send_command(new Command('punsubscribe', arr, callback, call_on_write));
|
|
586
588
|
};
|
|
587
589
|
|
|
588
590
|
Multi.prototype.punsubscribe = Multi.prototype.PUNSUBSCRIBE = function punsubscribe () {
|
|
@@ -610,6 +612,6 @@ Multi.prototype.punsubscribe = Multi.prototype.PUNSUBSCRIBE = function punsubscr
|
|
|
610
612
|
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
|
|
611
613
|
self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
|
|
612
614
|
};
|
|
613
|
-
this.queue.push(
|
|
615
|
+
this.queue.push(new Command('punsubscribe', arr, callback, call_on_write));
|
|
614
616
|
return this;
|
|
615
617
|
};
|
package/lib/multi.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var Queue = require('double-ended-queue');
|
|
4
4
|
var utils = require('./utils');
|
|
5
|
+
var Command = require('./command');
|
|
5
6
|
|
|
6
7
|
function Multi (client, args) {
|
|
7
8
|
this._client = client;
|
|
@@ -20,18 +21,24 @@ function Multi (client, args) {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
function pipeline_transaction_command (self,
|
|
24
|
+
function pipeline_transaction_command (self, command_obj, index) {
|
|
24
25
|
// Queueing is done first, then the commands are executed
|
|
25
|
-
|
|
26
|
+
var tmp = command_obj.callback;
|
|
27
|
+
command_obj.callback = function (err, reply) {
|
|
26
28
|
// Ignore the multi command. This is applied by node_redis and the user does not benefit by it
|
|
27
29
|
if (err && index !== -1) {
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
+
if (tmp) {
|
|
31
|
+
tmp(err);
|
|
30
32
|
}
|
|
31
33
|
err.position = index;
|
|
32
34
|
self.errors.push(err);
|
|
33
35
|
}
|
|
34
|
-
|
|
36
|
+
// Keep track of who wants buffer responses:
|
|
37
|
+
// By the time the callback is called the command_obj got the buffer_args attribute attached
|
|
38
|
+
self.wants_buffers[index] = command_obj.buffer_args;
|
|
39
|
+
command_obj.callback = tmp;
|
|
40
|
+
};
|
|
41
|
+
self._client.internal_send_command(command_obj);
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
Multi.prototype.exec_atomic = Multi.prototype.EXEC_ATOMIC = Multi.prototype.execAtomic = function exec_atomic (callback) {
|
|
@@ -42,7 +49,7 @@ Multi.prototype.exec_atomic = Multi.prototype.EXEC_ATOMIC = Multi.prototype.exec
|
|
|
42
49
|
};
|
|
43
50
|
|
|
44
51
|
function multi_callback (self, err, replies) {
|
|
45
|
-
var i = 0,
|
|
52
|
+
var i = 0, command_obj;
|
|
46
53
|
|
|
47
54
|
if (err) {
|
|
48
55
|
err.errors = self.errors;
|
|
@@ -56,22 +63,22 @@ function multi_callback (self, err, replies) {
|
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
if (replies) {
|
|
59
|
-
while (
|
|
66
|
+
while (command_obj = self.queue.shift()) {
|
|
60
67
|
if (replies[i] instanceof Error) {
|
|
61
68
|
var match = replies[i].message.match(utils.err_code);
|
|
62
69
|
// LUA script could return user errors that don't behave like all other errors!
|
|
63
70
|
if (match) {
|
|
64
71
|
replies[i].code = match[1];
|
|
65
72
|
}
|
|
66
|
-
replies[i].command =
|
|
67
|
-
if (typeof
|
|
68
|
-
|
|
73
|
+
replies[i].command = command_obj.command.toUpperCase();
|
|
74
|
+
if (typeof command_obj.callback === 'function') {
|
|
75
|
+
command_obj.callback(replies[i]);
|
|
69
76
|
}
|
|
70
77
|
} else {
|
|
71
78
|
// If we asked for strings, even in detect_buffers mode, then return strings:
|
|
72
|
-
replies[i] = self._client.handle_reply(replies[i],
|
|
73
|
-
if (typeof
|
|
74
|
-
|
|
79
|
+
replies[i] = self._client.handle_reply(replies[i], command_obj.command, self.wants_buffers[i]);
|
|
80
|
+
if (typeof command_obj.callback === 'function') {
|
|
81
|
+
command_obj.callback(null, replies[i]);
|
|
75
82
|
}
|
|
76
83
|
}
|
|
77
84
|
i++;
|
|
@@ -98,30 +105,16 @@ Multi.prototype.exec_transaction = function exec_transaction (callback) {
|
|
|
98
105
|
self.callback = callback;
|
|
99
106
|
self._client.cork();
|
|
100
107
|
self.wants_buffers = new Array(len);
|
|
101
|
-
pipeline_transaction_command(self, 'multi', [], -1);
|
|
108
|
+
pipeline_transaction_command(self, new Command('multi', []), -1);
|
|
102
109
|
// Drain queue, callback will catch 'QUEUED' or error
|
|
103
110
|
for (var index = 0; index < len; index++) {
|
|
104
111
|
// The commands may not be shifted off, since they are needed in the result handler
|
|
105
|
-
|
|
106
|
-
var command = command_obj[0];
|
|
107
|
-
var cb = command_obj[2];
|
|
108
|
-
var call_on_write = command_obj.length === 4 ? command_obj[3] : undefined;
|
|
109
|
-
// Keep track of who wants buffer responses:
|
|
110
|
-
if (self._client.options.detect_buffers) {
|
|
111
|
-
self.wants_buffers[index] = false;
|
|
112
|
-
for (var i = 0; i < command_obj[1].length; i += 1) {
|
|
113
|
-
if (command_obj[1][i] instanceof Buffer) {
|
|
114
|
-
self.wants_buffers[index] = true;
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
pipeline_transaction_command(self, command, command_obj[1], index, cb, call_on_write);
|
|
112
|
+
pipeline_transaction_command(self, self.queue.get(index), index);
|
|
120
113
|
}
|
|
121
114
|
|
|
122
|
-
self._client.internal_send_command('exec', [], function (err, replies) {
|
|
115
|
+
self._client.internal_send_command(new Command('exec', [], function (err, replies) {
|
|
123
116
|
multi_callback(self, err, replies);
|
|
124
|
-
});
|
|
117
|
+
}));
|
|
125
118
|
self._client.uncork();
|
|
126
119
|
return !self._client.should_buffer;
|
|
127
120
|
};
|
|
@@ -144,16 +137,17 @@ Multi.prototype.exec = Multi.prototype.EXEC = Multi.prototype.exec_batch = funct
|
|
|
144
137
|
var len = self.queue.length;
|
|
145
138
|
var index = 0;
|
|
146
139
|
var command_obj;
|
|
140
|
+
if (len === 0) {
|
|
141
|
+
utils.reply_in_order(self._client, callback, null, []);
|
|
142
|
+
return !self._client.should_buffer;
|
|
143
|
+
}
|
|
147
144
|
self._client.cork();
|
|
148
145
|
if (!callback) {
|
|
149
146
|
while (command_obj = self.queue.shift()) {
|
|
150
|
-
self._client.internal_send_command(command_obj
|
|
147
|
+
self._client.internal_send_command(command_obj);
|
|
151
148
|
}
|
|
152
149
|
self._client.uncork();
|
|
153
150
|
return !self._client.should_buffer;
|
|
154
|
-
} else if (len === 0) {
|
|
155
|
-
utils.reply_in_order(self._client, callback, null, []);
|
|
156
|
-
return !self._client.should_buffer;
|
|
157
151
|
}
|
|
158
152
|
var callback_without_own_cb = function (err, res) {
|
|
159
153
|
if (err) {
|
|
@@ -175,18 +169,15 @@ Multi.prototype.exec = Multi.prototype.EXEC = Multi.prototype.exec_batch = funct
|
|
|
175
169
|
};
|
|
176
170
|
self.results = [];
|
|
177
171
|
while (command_obj = self.queue.shift()) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
var cb;
|
|
181
|
-
if (typeof command_obj[2] === 'function') {
|
|
182
|
-
cb = batch_callback(self, command_obj[2], index);
|
|
172
|
+
if (typeof command_obj.callback === 'function') {
|
|
173
|
+
command_obj.callback = batch_callback(self, command_obj.callback, index);
|
|
183
174
|
} else {
|
|
184
|
-
|
|
175
|
+
command_obj.callback = callback_without_own_cb;
|
|
185
176
|
}
|
|
186
177
|
if (typeof callback === 'function' && index === len - 1) {
|
|
187
|
-
|
|
178
|
+
command_obj.callback = last_callback(command_obj.callback);
|
|
188
179
|
}
|
|
189
|
-
this._client.internal_send_command(
|
|
180
|
+
this._client.internal_send_command(command_obj);
|
|
190
181
|
index++;
|
|
191
182
|
}
|
|
192
183
|
self._client.uncork();
|
package/lib/utils.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var RawObject = require('./rawObject');
|
|
4
|
-
|
|
5
3
|
// hgetall converts its replies to an Object. If the reply is empty, null is returned.
|
|
6
4
|
// These function are only called with internal data and have therefore always the same instanceof X
|
|
7
5
|
function replyToObject (reply) {
|
|
@@ -9,7 +7,7 @@ function replyToObject (reply) {
|
|
|
9
7
|
if (reply.length === 0 || !(reply instanceof Array)) {
|
|
10
8
|
return null;
|
|
11
9
|
}
|
|
12
|
-
var obj =
|
|
10
|
+
var obj = {};
|
|
13
11
|
for (var i = 0; i < reply.length; i += 2) {
|
|
14
12
|
obj[reply[i].toString('binary')] = reply[i + 1];
|
|
15
13
|
}
|
|
@@ -59,14 +57,18 @@ function clone (obj) {
|
|
|
59
57
|
var elems = Object.keys(obj);
|
|
60
58
|
var elem;
|
|
61
59
|
while (elem = elems.pop()) {
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
if (elem === 'tls') { // special handle tls
|
|
61
|
+
copy[elem] = obj[elem];
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// Accept camelCase options and convert them to snake_case
|
|
65
|
+
var snake_case = elem.replace(/[A-Z][^A-Z]/g, '_$&').toLowerCase();
|
|
64
66
|
// If camelCase is detected, pass it to the client, so all variables are going to be camelCased
|
|
65
67
|
// There are no deep nested options objects yet, but let's handle this future proof
|
|
66
|
-
if (
|
|
68
|
+
if (snake_case !== elem.toLowerCase()) {
|
|
67
69
|
camelCase = true;
|
|
68
70
|
}
|
|
69
|
-
copy[
|
|
71
|
+
copy[snake_case] = clone(obj[elem]);
|
|
70
72
|
}
|
|
71
73
|
return copy;
|
|
72
74
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redis",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.3",
|
|
4
4
|
"description": "Redis client library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"database",
|
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
"coverage": "nyc report --reporter=html",
|
|
22
22
|
"benchmark": "node benchmarks/multi_bench.js",
|
|
23
23
|
"test": "nyc --cache mocha ./test/*.js ./test/commands/*.js --timeout=8000",
|
|
24
|
-
"posttest": "eslint . --fix"
|
|
24
|
+
"posttest": "eslint . --fix && npm run coverage",
|
|
25
|
+
"compare": "node benchmarks/diff_multi_bench_output.js beforeBench.txt afterBench.txt"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"double-ended-queue": "^2.1.0-0",
|
|
28
29
|
"redis-commands": "^1.2.0",
|
|
29
|
-
"redis-parser": "^
|
|
30
|
+
"redis-parser": "^2.0.0"
|
|
30
31
|
},
|
|
31
32
|
"engines": {
|
|
32
33
|
"node": ">=0.10.0"
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"eslint": "^2.5.0",
|
|
39
40
|
"metrics": "^0.1.9",
|
|
40
41
|
"mocha": "^2.3.2",
|
|
41
|
-
"nyc": "^
|
|
42
|
+
"nyc": "^8.3.0",
|
|
42
43
|
"tcp-port-used": "^0.1.2",
|
|
43
44
|
"uuid": "^2.0.1",
|
|
44
45
|
"win-spawn": "^2.0.0"
|
package/lib/rawObject.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Using a predefined object with this prototype is faster than calling `Object.create(null)` directly
|
|
4
|
-
// This is needed to make sure `__proto__` and similar reserved words can be used
|
|
5
|
-
function RawObject () {}
|
|
6
|
-
RawObject.prototype = Object.create(null);
|
|
7
|
-
|
|
8
|
-
module.exports = RawObject;
|