Browse files

Merge branch 'master' of github.com:dfield/07.21.2012

  • Loading branch information...
2 parents 98d23f6 + 024db62 commit d3c057c4598c25ec8639b7374a21aea30feb82b5 Jessica Liu committed Jul 22, 2012
Showing with 5,072 additions and 15 deletions.
  1. +13 −11 game.js
  2. +1 −0 node_modules/redis/.npmignore
  3. +638 −0 node_modules/redis/README.md
  4. +219 −0 node_modules/redis/changelog.md
  5. +5 −0 node_modules/redis/examples/auth.js
  6. +33 −0 node_modules/redis/examples/backpressure_drain.js
  7. +9 −0 node_modules/redis/examples/eval.js
  8. +24 −0 node_modules/redis/examples/extend.js
  9. +32 −0 node_modules/redis/examples/file.js
  10. +5 −0 node_modules/redis/examples/mget.js
  11. +10 −0 node_modules/redis/examples/monitor.js
  12. +46 −0 node_modules/redis/examples/multi.js
  13. +29 −0 node_modules/redis/examples/multi2.js
  14. +33 −0 node_modules/redis/examples/psubscribe.js
  15. +41 −0 node_modules/redis/examples/pub_sub.js
  16. +24 −0 node_modules/redis/examples/simple.js
  17. +17 −0 node_modules/redis/examples/sort.js
  18. +15 −0 node_modules/redis/examples/subqueries.js
  19. +19 −0 node_modules/redis/examples/subquery.js
  20. +29 −0 node_modules/redis/examples/unix_socket.js
  21. +31 −0 node_modules/redis/examples/web_server.js
  22. +39 −0 node_modules/redis/generate_commands.js
  23. +1,030 −0 node_modules/redis/index.js
  24. +126 −0 node_modules/redis/lib/commands.js
  25. +46 −0 node_modules/redis/lib/parser/hiredis.js
  26. +317 −0 node_modules/redis/lib/parser/javascript.js
  27. +61 −0 node_modules/redis/lib/queue.js
  28. +12 −0 node_modules/redis/lib/to_array.js
  29. +11 −0 node_modules/redis/lib/util.js
  30. +225 −0 node_modules/redis/multi_bench.js
  31. +22 −0 node_modules/redis/package.json
  32. +1,416 −0 node_modules/redis/test.js
  33. +89 −0 node_modules/redis/tests/buffer_bench.js
  34. +38 −0 node_modules/redis/tests/hiredis_parser.js
  35. +14 −0 node_modules/redis/tests/re_sub_test.js
  36. +29 −0 node_modules/redis/tests/reconnect_test.js
  37. +16 −0 node_modules/redis/tests/stress/codec.js
  38. +38 −0 node_modules/redis/tests/stress/pubsub/pub.js
  39. +10 −0 node_modules/redis/tests/stress/pubsub/run
  40. +23 −0 node_modules/redis/tests/stress/pubsub/server.js
  41. +49 −0 node_modules/redis/tests/stress/rpushblpop/pub.js
  42. +6 −0 node_modules/redis/tests/stress/rpushblpop/run
  43. +30 −0 node_modules/redis/tests/stress/rpushblpop/server.js
  44. +13 −0 node_modules/redis/tests/stress/speed/00
  45. +13 −0 node_modules/redis/tests/stress/speed/plot
  46. BIN node_modules/redis/tests/stress/speed/size-rate.png
  47. +84 −0 node_modules/redis/tests/stress/speed/speed.js
  48. +18 −0 node_modules/redis/tests/sub_quit_test.js
  49. +1 −1 public/js/player.js
  50. +9 −0 public/js/wiki.js
  51. +14 −3 server.js
View
24 game.js
@@ -135,23 +135,25 @@ Game.prototype.removeClient = function(socket) {
Game.prototype.setArticle = function(playerId, articleId) {
var player = this.world.players[playerId];
- player.article = new Article(this.graph[articleId].page_title, articleId);
+ var currentArticle = JSON.parse(this.redis.get(articleId));
+ player.article = new Article(currentArticle.page_title, articleId);
this.update();
}
-Game.prototype.getArticles = function(socket) {
+Game.prototype.getArticles = function(socket, callback) {
var player = this.world.players[socket.playerId];
var currentArticleId = player.article.id;
var articlesData = {};
- var connectedArticles = this.graph[currentArticleId].page_links;
- for (var i = 0; i < connectedArticles.length && i < 10; i++) {
- var article = connectedArticles[i];
- var articleData = {"id": article.id, "name": article.title};
- articlesData[article.id] = articleData;
- }
- console.log(articlesData);
-
- return articlesData;
+ this.redis.get(currentArticleId, function (err, reply) {
+ var currentArticle = JSON.parse(reply);
+ var connectedArticles = currentArticle.page_links;
+ for (var i = 0; i < connectedArticles.length && i < 10; i++) {
+ var article = connectedArticles[i];
+ var articleData = {"id": article.id, "name": article.title};
+ articlesData[article.id] = articleData;
+ }
+ callback(articlesData);
+ });
}
exports.Game = Game;
View
1 node_modules/redis/.npmignore
@@ -0,0 +1 @@
+node_modules
View
638 node_modules/redis/README.md
@@ -0,0 +1,638 @@
+redis - a node.js redis client
+===========================
+
+This is a complete Redis client for node.js. It supports all Redis commands, including many recently added commands like EVAL from
+experimental Redis server branches.
+
+
+Install with:
+
+ npm install redis
+
+Pieter Noordhuis has provided a binding to the official `hiredis` C library, which is non-blocking and fast. To use `hiredis`, do:
+
+ npm install hiredis redis
+
+If `hiredis` is installed, `node_redis` will use it by default. Otherwise, a pure JavaScript parser will be used.
+
+If you use `hiredis`, be sure to rebuild it whenever you upgrade your version of node. There are mysterious failures that can
+happen between node and native code modules after a node upgrade.
+
+
+## Usage
+
+Simple example, included as `examples/simple.js`:
+
+```js
+ var redis = require("redis"),
+ client = redis.createClient();
+
+ // if you'd like to select database 3, instead of 0 (default), call
+ // client.select(3, function() { /* ... */ });
+
+ client.on("error", function (err) {
+ console.log("Error " + err);
+ });
+
+ client.set("string key", "string val", redis.print);
+ client.hset("hash key", "hashtest 1", "some value", redis.print);
+ client.hset(["hash key", "hashtest 2", "some other value"], redis.print);
+ client.hkeys("hash key", function (err, replies) {
+ console.log(replies.length + " replies:");
+ replies.forEach(function (reply, i) {
+ console.log(" " + i + ": " + reply);
+ });
+ client.quit();
+ });
+```
+
+This will display:
+
+ mjr:~/work/node_redis (master)$ node example.js
+ Reply: OK
+ Reply: 0
+ Reply: 0
+ 2 replies:
+ 0: hashtest 1
+ 1: hashtest 2
+ mjr:~/work/node_redis (master)$
+
+
+## Performance
+
+Here are typical results of `multi_bench.js` which is similar to `redis-benchmark` from the Redis distribution.
+It uses 50 concurrent connections with no pipelining.
+
+JavaScript parser:
+
+ PING: 20000 ops 42283.30 ops/sec 0/5/1.182
+ SET: 20000 ops 32948.93 ops/sec 1/7/1.515
+ GET: 20000 ops 28694.40 ops/sec 0/9/1.740
+ INCR: 20000 ops 39370.08 ops/sec 0/8/1.269
+ LPUSH: 20000 ops 36429.87 ops/sec 0/8/1.370
+ LRANGE (10 elements): 20000 ops 9891.20 ops/sec 1/9/5.048
+ LRANGE (100 elements): 20000 ops 1384.56 ops/sec 10/91/36.072
+
+hiredis parser:
+
+ PING: 20000 ops 46189.38 ops/sec 1/4/1.082
+ SET: 20000 ops 41237.11 ops/sec 0/6/1.210
+ GET: 20000 ops 39682.54 ops/sec 1/7/1.257
+ INCR: 20000 ops 40080.16 ops/sec 0/8/1.242
+ LPUSH: 20000 ops 41152.26 ops/sec 0/3/1.212
+ LRANGE (10 elements): 20000 ops 36563.07 ops/sec 1/8/1.363
+ LRANGE (100 elements): 20000 ops 21834.06 ops/sec 0/9/2.287
+
+The performance of `node_redis` improves dramatically with pipelining, which happens automatically in most normal programs.
+
+
+### Sending Commands
+
+Each Redis command is exposed as a function on the `client` object.
+All functions take either an `args` Array plus optional `callback` Function or
+a variable number of individual arguments followed by an optional callback.
+Here is an example of passing an array of arguments and a callback:
+
+ client.mset(["test keys 1", "test val 1", "test keys 2", "test val 2"], function (err, res) {});
+
+Here is that same call in the second style:
+
+ client.mset("test keys 1", "test val 1", "test keys 2", "test val 2", function (err, res) {});
+
+Note that in either form the `callback` is optional:
+
+ client.set("some key", "some val");
+ client.set(["some other key", "some val"]);
+
+For a list of Redis commands, see [Redis Command Reference](http://redis.io/commands)
+
+The commands can be specified in uppercase or lowercase for convenience. `client.get()` is the same as `client.GET()`.
+
+Minimal parsing is done on the replies. Commands that return a single line reply return JavaScript Strings,
+integer replies return JavaScript Numbers, "bulk" replies return node Buffers, and "multi bulk" replies return a
+JavaScript Array of node Buffers. `HGETALL` returns an Object with Buffers keyed by the hash keys.
+
+# API
+
+## Connection Events
+
+`client` will emit some events about the state of the connection to the Redis server.
+
+### "ready"
+
+`client` will emit `ready` a connection is established to the Redis server and the server reports
+that it is ready to receive commands. Commands issued before the `ready` event are queued,
+then replayed just before this event is emitted.
+
+### "connect"
+
+`client` will emit `connect` at the same time as it emits `ready` unless `client.options.no_ready_check`
+is set. If this options is set, `connect` will be emitted when the stream is connected, and then
+you are free to try to send commands.
+
+### "error"
+
+`client` will emit `error` when encountering an error connecting to the Redis server.
+
+Note that "error" is a special event type in node. If there are no listeners for an
+"error" event, node will exit. This is usually what you want, but it can lead to some
+cryptic error messages like this:
+
+ mjr:~/work/node_redis (master)$ node example.js
+
+ node.js:50
+ throw e;
+ ^
+ Error: ECONNREFUSED, Connection refused
+ at IOWatcher.callback (net:870:22)
+ at node.js:607:9
+
+Not very useful in diagnosing the problem, but if your program isn't ready to handle this,
+it is probably the right thing to just exit.
+
+`client` will also emit `error` if an exception is thrown inside of `node_redis` for whatever reason.
+It would be nice to distinguish these two cases.
+
+### "end"
+
+`client` will emit `end` when an established Redis server connection has closed.
+
+### "drain"
+
+`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. Right now,
+you need to check `client.command_queue.length` to decide when to reduce your send rate. Then you can
+resume sending when you get `drain`.
+
+### "idle"
+
+`client` will emit `idle` when there are no outstanding commands that are awaiting a response.
+
+## redis.createClient(port, host, options)
+
+Create a new client connection. `port` defaults to `6379` and `host` defaults
+to `127.0.0.1`. If you have `redis-server` running on the same computer as node, then the defaults for
+port and host are probably fine. `options` in an object with the following possible properties:
+
+* `parser`: which Redis protocol reply parser to use. Defaults to `hiredis` if that module is installed.
+This may also be set to `javascript`.
+* `return_buffers`: defaults to `false`. If set to `true`, then all replies will be sent to callbacks as node Buffer
+objects instead of JavaScript Strings.
+* `detect_buffers`: default to `false`. If set to `true`, then replies will be sent to callbacks as node Buffer objects
+if any of the input arguments to the original command were Buffer objects.
+This option lets you switch between Buffers and Strings on a per-command basis, whereas `return_buffers` applies to
+every command on a client.
+* `socket_nodelay`: defaults to `true`. Whether to call setNoDelay() on the TCP stream, which disables the
+Nagle algorithm on the underlying socket. Setting this option to `false` can result in additional throughput at the
+cost of more latency. Most applications will want this set to `true`.
+* `no_ready_check`: defaults to `false`. When a connection is established to the Redis server, the server might still
+be loading the database from disk. While loading, the server 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.
+
+```js
+ var redis = require("redis"),
+ client = redis.createClient(null, null, {detect_buffers: true});
+
+ client.set("foo_rand000000000000", "OK");
+
+ // This will return a JavaScript String
+ client.get("foo_rand000000000000", function (err, reply) {
+ console.log(reply.toString()); // Will print `OK`
+ });
+
+ // This will return a Buffer since original key is specified as a Buffer
+ client.get(new Buffer("foo_rand000000000000"), function (err, reply) {
+ console.log(reply.toString()); // Will print `<Buffer 4f 4b>`
+ });
+ client.end();
+```
+
+`createClient()` returns a `RedisClient` object that is named `client` in all of the examples here.
+
+## client.auth(password, callback)
+
+When connecting to Redis servers that require authentication, the `AUTH` command must be sent as the
+first command after connecting. This can be tricky to coordinate with reconnections, the ready check,
+etc. To make this easier, `client.auth()` stashes `password` and will send it after each connection,
+including reconnections. `callback` is invoked only once, after the response to the very first
+`AUTH` command sent.
+NOTE: Your call to `client.auth()` should not be inside the ready handler. If
+you are doing this wrong, `client` will emit an error that looks
+something like this `Error: Ready check failed: ERR operation not permitted`.
+
+## client.end()
+
+Forcibly close the connection to the Redis server. Note that this does not wait until all replies have been parsed.
+If you want to exit cleanly, call `client.quit()` to send the `QUIT` command after you have handled all replies.
+
+This example closes the connection to the Redis server before the replies have been read. You probably don't
+want to do this:
+
+```js
+ var redis = require("redis"),
+ client = redis.createClient();
+
+ client.set("foo_rand000000000000", "some fantastic value");
+ client.get("foo_rand000000000000", function (err, reply) {
+ console.log(reply.toString());
+ });
+ client.end();
+```
+
+`client.end()` is useful for timeout cases where something is stuck or taking too long and you want
+to start over.
+
+## Friendlier hash commands
+
+Most Redis commands take a single String or an Array of Strings as arguments, and replies are sent back as a single String or an Array of Strings.
+When dealing with hash values, there are a couple of useful exceptions to this.
+
+### client.hgetall(hash)
+
+The reply from an HGETALL command will be converted into a JavaScript Object by `node_redis`. That way you can interact
+with the responses using JavaScript syntax.
+
+Example:
+
+ client.hmset("hosts", "mjr", "1", "another", "23", "home", "1234");
+ client.hgetall("hosts", function (err, obj) {
+ console.dir(obj);
+ });
+
+Output:
+
+ { mjr: '1', another: '23', home: '1234' }
+
+### client.hmset(hash, obj, [callback])
+
+Multiple values in a hash can be set by supplying an object:
+
+ client.HMSET(key2, {
+ "0123456789": "abcdefghij",
+ "some manner of key": "a type of value"
+ });
+
+The properties and values of this Object will be set as keys and values in the Redis hash.
+
+### client.hmset(hash, key1, val1, ... keyn, valn, [callback])
+
+Multiple values may also be set by supplying a list:
+
+ client.HMSET(key1, "0123456789", "abcdefghij", "some manner of key", "a type of value");
+
+
+## Publish / Subscribe
+
+Here is a simple example of the API for publish / subscribe. This program opens two
+client connections, subscribes to a channel on one of them, and publishes to that
+channel on the other:
+
+```js
+ var redis = require("redis"),
+ client1 = redis.createClient(), client2 = redis.createClient(),
+ msg_count = 0;
+
+ client1.on("subscribe", function (channel, count) {
+ client2.publish("a nice channel", "I am sending a message.");
+ client2.publish("a nice channel", "I am sending a second message.");
+ client2.publish("a nice channel", "I am sending my last message.");
+ });
+
+ client1.on("message", function (channel, message) {
+ console.log("client1 channel " + channel + ": " + message);
+ msg_count += 1;
+ if (msg_count === 3) {
+ client1.unsubscribe();
+ client1.end();
+ client2.end();
+ }
+ });
+
+ client1.incr("did a thing");
+ client1.subscribe("a nice channel");
+```
+
+When a client issues a `SUBSCRIBE` or `PSUBSCRIBE`, that connection is put into "pub/sub" mode.
+At that point, only commands that modify the subscription set are valid. When the subscription
+set is empty, the connection is put back into regular mode.
+
+If you need to send regular commands to Redis while in pub/sub mode, just open another connection.
+
+## Pub / Sub Events
+
+If a client has subscriptions active, it may emit these events:
+
+### "message" (channel, message)
+
+Client will emit `message` for every message received that matches an active subscription.
+Listeners are passed the channel name as `channel` and the message Buffer as `message`.
+
+### "pmessage" (pattern, channel, message)
+
+Client will emit `pmessage` for every message received that matches an active subscription pattern.
+Listeners are passed the original pattern used with `PSUBSCRIBE` as `pattern`, the sending channel
+name as `channel`, and the message Buffer as `message`.
+
+### "subscribe" (channel, count)
+
+Client will emit `subscribe` in response to a `SUBSCRIBE` command. Listeners are passed the
+channel name as `channel` and the new count of subscriptions for this client as `count`.
+
+### "psubscribe" (pattern, count)
+
+Client will emit `psubscribe` in response to a `PSUBSCRIBE` command. Listeners are passed the
+original pattern as `pattern`, and the new count of subscriptions for this client as `count`.
+
+### "unsubscribe" (channel, count)
+
+Client will emit `unsubscribe` in response to a `UNSUBSCRIBE` command. Listeners are passed the
+channel name as `channel` and the new count of subscriptions for this client as `count`. When
+`count` is 0, this client has left pub/sub mode and no more pub/sub events will be emitted.
+
+### "punsubscribe" (pattern, count)
+
+Client will emit `punsubscribe` in response to a `PUNSUBSCRIBE` command. Listeners are passed the
+channel name as `channel` and the new count of subscriptions for this client as `count`. When
+`count` is 0, this client has left pub/sub mode and no more pub/sub events will be emitted.
+
+## client.multi([commands])
+
+`MULTI` commands are queued up until an `EXEC` is issued, and then all commands are run atomically by
+Redis. The interface in `node_redis` is to return an individual `Multi` object by calling `client.multi()`.
+
+```js
+ var redis = require("./index"),
+ client = redis.createClient(), set_size = 20;
+
+ client.sadd("bigset", "a member");
+ client.sadd("bigset", "another member");
+
+ while (set_size > 0) {
+ client.sadd("bigset", "member " + set_size);
+ set_size -= 1;
+ }
+
+ // multi chain with an individual callback
+ client.multi()
+ .scard("bigset")
+ .smembers("bigset")
+ .keys("*", function (err, replies) {
+ // NOTE: code in this callback is NOT atomic
+ // this only happens after the the .exec call finishes.
+ client.mget(replies, redis.print);
+ })
+ .dbsize()
+ .exec(function (err, replies) {
+ console.log("MULTI got " + replies.length + " replies");
+ replies.forEach(function (reply, index) {
+ console.log("Reply " + index + ": " + reply.toString());
+ });
+ });
+```
+
+`client.multi()` is a constructor that returns a `Multi` object. `Multi` objects share all of the
+same command methods as `client` objects do. Commands are queued up inside the `Multi` object
+until `Multi.exec()` is invoked.
+
+You can either chain together `MULTI` commands as in the above example, or you can queue individual
+commands while still sending regular client command as in this example:
+
+```js
+ var redis = require("redis"),
+ client = redis.createClient(), multi;
+
+ // start a separate multi command queue
+ multi = client.multi();
+ multi.incr("incr thing", redis.print);
+ multi.incr("incr other thing", redis.print);
+
+ // runs immediately
+ client.mset("incr thing", 100, "incr other thing", 1, redis.print);
+
+ // drains multi queue and runs atomically
+ multi.exec(function (err, replies) {
+ console.log(replies); // 101, 2
+ });
+
+ // you can re-run the same transaction if you like
+ multi.exec(function (err, replies) {
+ console.log(replies); // 102, 3
+ client.quit();
+ });
+```
+
+In addition to adding commands to the `MULTI` queue individually, you can also pass an array
+of commands and arguments to the constructor:
+
+```js
+ var redis = require("redis"),
+ client = redis.createClient(), multi;
+
+ client.multi([
+ ["mget", "multifoo", "multibar", redis.print],
+ ["incr", "multifoo"],
+ ["incr", "multibar"]
+ ]).exec(function (err, replies) {
+ console.log(replies);
+ });
+```
+
+
+## Monitor mode
+
+Redis supports the `MONITOR` command, which lets you see all commands received by the Redis server
+across all client connections, including from other client libraries and other computers.
+
+After you send the `MONITOR` command, no other commands are valid on that connection. `node_redis`
+will emit a `monitor` event for every new monitor message that comes across. The callback for the
+`monitor` event takes a timestamp from the Redis server and an array of command arguments.
+
+Here is a simple example:
+
+```js
+ var client = require("redis").createClient(),
+ util = require("util");
+
+ client.monitor(function (err, res) {
+ console.log("Entering monitoring mode.");
+ });
+
+ client.on("monitor", function (time, args) {
+ console.log(time + ": " + util.inspect(args));
+ });
+```
+
+# Extras
+
+Some other things you might like to know about.
+
+## client.server_info
+
+After the ready probe completes, the results from the INFO command are saved in the `client.server_info`
+object.
+
+The `versions` key contains an array of the elements of the version string for easy comparison.
+
+ > client.server_info.redis_version
+ '2.3.0'
+ > client.server_info.versions
+ [ 2, 3, 0 ]
+
+## redis.print()
+
+A handy callback function for displaying return values when testing. Example:
+
+```js
+ var redis = require("redis"),
+ client = redis.createClient();
+
+ client.on("connect", function () {
+ client.set("foo_rand000000000000", "some fantastic value", redis.print);
+ client.get("foo_rand000000000000", redis.print);
+ });
+```
+
+This will print:
+
+ Reply: OK
+ Reply: some fantastic value
+
+Note that this program will not exit cleanly because the client is still connected.
+
+## redis.debug_mode
+
+Boolean to enable debug mode and protocol tracing.
+
+```js
+ var redis = require("redis"),
+ client = redis.createClient();
+
+ redis.debug_mode = true;
+
+ client.on("connect", function () {
+ client.set("foo_rand000000000000", "some fantastic value");
+ });
+```
+
+This will display:
+
+ mjr:~/work/node_redis (master)$ node ~/example.js
+ send command: *3
+ $3
+ SET
+ $20
+ foo_rand000000000000
+ $20
+ some fantastic value
+
+ on_data: +OK
+
+`send command` is data sent into Redis and `on_data` is data received from Redis.
+
+## client.send_command(command_name, args, callback)
+
+Used internally to send commands to Redis. For convenience, nearly all commands that are published on the Redis
+Wiki have been added to the `client` object. However, if I missed any, or if new commands are introduced before
+this library is updated, you can use `send_command()` to send arbitrary commands to Redis.
+
+All commands are sent as multi-bulk commands. `args` can either be an Array of arguments, or omitted.
+
+## client.connected
+
+Boolean tracking the state of the connection to the Redis server.
+
+## client.command_queue.length
+
+The number of commands that have been sent to the Redis server but not yet replied to. You can use this to
+enforce some kind of maximum queue depth for commands while connected.
+
+Don't mess with `client.command_queue` though unless you really know what you are doing.
+
+## client.offline_queue.length
+
+The number of commands that have been queued up for a future connection. You can use this to enforce
+some kind of maximum queue depth for pre-connection commands.
+
+## client.retry_delay
+
+Current delay in milliseconds before a connection retry will be attempted. This starts at `250`.
+
+## client.retry_backoff
+
+Multiplier for future retry timeouts. This should be larger than 1 to add more time between retries.
+Defaults to 1.7. The default initial connection retry is 250, so the second retry will be 425, followed by 723.5, etc.
+
+
+## TODO
+
+Better tests for auth, disconnect/reconnect, and all combinations thereof.
+
+Stream large set/get values into and out of Redis. Otherwise the entire value must be in node's memory.
+
+Performance can be better for very large values.
+
+I think there are more performance improvements left in there for smaller values, especially for large lists of small values.
+
+## Contributors
+
+Some people have have added features and fixed bugs in `node_redis` other than me.
+
+In alphabetical order, they are:
+
+* [Aivo Paas](https://github.com/aivopaas)
+* [Andy Ray](https://github.com/DelvarWorld)
+* Daniele
+* [Dave Hoover](https://github.com/redsquirrel)
+* [David Trejo](https://github.com/DTrejo)
+* Dayananda Nanjundappa
+* [Felix Geisendörfer](https://github.com/felixge)
+* [Hank Sims](https://github.com/hanksims)
+* [Ian Babrou](https://github.com/bobrik)
+* [Isaac Z. Schlueter](https://github.com/isaacs)
+* [Louis-Philippe Perron](https://github.com/lp)
+* [Maksim Lin](https://github.com/maks)
+* [Marcus Westin](https://github.com/marcuswestin)
+* [Mark Dawson](https://github.com/markdaws)
+* [Nithesh Chandra Gupta Mittapally](https://github.com/nithesh)
+* [Orion Henry](https://github.com/orionz)
+* [Owen Smith](https://github.com/orls)
+* [Paul Carey](https://github.com/paulcarey)
+* [Philip Tellis](https://github.com/bluesmoon)
+* [Pieter Noordhuis](https://github.com/pietern)
+* [Rick Olson](https://github.com/technoweenie)
+* [Tim Smart](https://github.com/Tim-Smart)
+* [TJ Holowaychuk](https://github.com/visionmedia)
+* [Umair Siddique](https://github.com/umairsiddique)
+* [Vladimir Dronnikov](https://github.com/dvv)
+* [Zachary Scott](https://github.com/zzak)
+
+Thanks.
+
+## LICENSE - "MIT License"
+
+Copyright (c) 2010 Matthew Ranney, http://ranney.com/
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+![spacer](http://ranney.com/1px.gif)
View
219 node_modules/redis/changelog.md
@@ -0,0 +1,219 @@
+Changelog
+=========
+
+## v0.7.2 - April 29, 2012
+
+Many contributed fixes. Thank you, contributors.
+
+* [GH-190] - pub/sub mode fix (Brian Noguchi)
+* [GH-165] - parser selection fix (TEHEK)
+* numerous documentation and examples updates
+* auth errors emit Errors instead of Strings (David Trejo)
+
+## v0.7.1 - November 15, 2011
+
+Fix regression in reconnect logic.
+
+Very much need automated tests for reconnection and queue logic.
+
+## v0.7.0 - November 14, 2011
+
+Many contributed fixes. Thanks everybody.
+
+* [GH-127] - properly re-initialize parser on reconnect
+* [GH-136] - handle passing undefined as callback (Ian Babrou)
+* [GH-139] - properly handle exceptions thrown in pub/sub event handlers (Felix Geisendörfer)
+* [GH-141] - detect closing state on stream error (Felix Geisendörfer)
+* [GH-142] - re-select database on reconnection (Jean-Hugues Pinson)
+* [GH-146] - add sort example (Maksim Lin)
+
+Some more goodies:
+
+* Fix bugs with node 0.6
+* Performance improvements
+* New version of `multi_bench.js` that tests more realistic scenarios
+* [GH-140] - support optional callback for subscribe commands
+* Properly flush and error out command queue when connection fails
+* Initial work on reconnection thresholds
+
+## v0.6.7 - July 30, 2011
+
+(accidentally skipped v0.6.6)
+
+Fix and test for [GH-123]
+
+Passing an Array as as the last argument should expand as users
+expect. The old behavior was to coerce the arguments into Strings,
+which did surprising things with Arrays.
+
+## v0.6.5 - July 6, 2011
+
+Contributed changes:
+
+* Support SlowBuffers (Umair Siddique)
+* Add Multi to exports (Louis-Philippe Perron)
+* Fix for drain event calculation (Vladimir Dronnikov)
+
+Thanks!
+
+## v0.6.4 - June 30, 2011
+
+Fix bug with optional callbacks for hmset.
+
+## v0.6.2 - June 30, 2011
+
+Bugs fixed:
+
+* authentication retry while server is loading db (danmaz74) [GH-101]
+* command arguments processing issue with arrays
+
+New features:
+
+* Auto update of new commands from redis.io (Dave Hoover)
+* Performance improvements and backpressure controls.
+* Commands now return the true/false value from the underlying socket write(s).
+* Implement command_queue high water and low water for more better control of queueing.
+
+See `examples/backpressure_drain.js` for more information.
+
+## v0.6.1 - June 29, 2011
+
+Add support and tests for Redis scripting through EXEC command.
+
+Bug fix for monitor mode. (forddg)
+
+Auto update of new commands from redis.io (Dave Hoover)
+
+## v0.6.0 - April 21, 2011
+
+Lots of bugs fixed.
+
+* connection error did not properly trigger reconnection logic [GH-85]
+* client.hmget(key, [val1, val2]) was not expanding properly [GH-66]
+* client.quit() while in pub/sub mode would throw an error [GH-87]
+* client.multi(['hmset', 'key', {foo: 'bar'}]) fails [GH-92]
+* unsubscribe before subscribe would make things very confused [GH-88]
+* Add BRPOPLPUSH [GH-79]
+
+## v0.5.11 - April 7, 2011
+
+Added DISCARD
+
+I originally didn't think DISCARD would do anything here because of the clever MULTI interface, but somebody
+pointed out to me that DISCARD can be used to flush the WATCH set.
+
+## v0.5.10 - April 6, 2011
+
+Added HVALS
+
+## v0.5.9 - March 14, 2011
+
+Fix bug with empty Array arguments - Andy Ray
+
+## v0.5.8 - March 14, 2011
+
+Add `MONITOR` command and special monitor command reply parsing.
+
+## v0.5.7 - February 27, 2011
+
+Add magical auth command.
+
+Authentication is now remembered by the client and will be automatically sent to the server
+on every connection, including any reconnections.
+
+## v0.5.6 - February 22, 2011
+
+Fix bug in ready check with `return_buffers` set to `true`.
+
+Thanks to Dean Mao and Austin Chau.
+
+## v0.5.5 - February 16, 2011
+
+Add probe for server readiness.
+
+When a Redis server starts up, it might take a while to load the dataset into memory.
+During this time, the server will accept connections, but will return errors for all non-INFO
+commands. Now node_redis will send an INFO command whenever it connects to a server.
+If the info command indicates that the server is not ready, the client will keep trying until
+the server is ready. Once it is ready, the client will emit a "ready" event as well as the
+"connect" event. The client will queue up all commands sent before the server is ready, just
+like it did before. When the server is ready, all offline/non-ready commands will be replayed.
+This should be backward compatible with previous versions.
+
+To disable this ready check behavior, set `options.no_ready_check` when creating the client.
+
+As a side effect of this change, the key/val params from the info command are available as
+`client.server_options`. Further, the version string is decomposed into individual elements
+in `client.server_options.versions`.
+
+## v0.5.4 - February 11, 2011
+
+Fix excess memory consumption from Queue backing store.
+
+Thanks to Gustaf Sjöberg.
+
+## v0.5.3 - February 5, 2011
+
+Fix multi/exec error reply callback logic.
+
+Thanks to Stella Laurenzo.
+
+## v0.5.2 - January 18, 2011
+
+Fix bug where unhandled error replies confuse the parser.
+
+## v0.5.1 - January 18, 2011
+
+Fix bug where subscribe commands would not handle redis-server startup error properly.
+
+## v0.5.0 - December 29, 2010
+
+Some bug fixes:
+
+* An important bug fix in reconnection logic. Previously, reply callbacks would be invoked twice after
+ a reconnect.
+* Changed error callback argument to be an actual Error object.
+
+New feature:
+
+* Add friendly syntax for HMSET using an object.
+
+## v0.4.1 - December 8, 2010
+
+Remove warning about missing hiredis. You probably do want it though.
+
+## v0.4.0 - December 5, 2010
+
+Support for multiple response parsers and hiredis C library from Pieter Noordhuis.
+Return Strings instead of Buffers by default.
+Empty nested mb reply bug fix.
+
+## v0.3.9 - November 30, 2010
+
+Fix parser bug on failed EXECs.
+
+## v0.3.8 - November 10, 2010
+
+Fix for null MULTI response when WATCH condition fails.
+
+## v0.3.7 - November 9, 2010
+
+Add "drain" and "idle" events.
+
+## v0.3.6 - November 3, 2010
+
+Add all known Redis commands from Redis master, even ones that are coming in 2.2 and beyond.
+
+Send a friendlier "error" event message on stream errors like connection refused / reset.
+
+## v0.3.5 - October 21, 2010
+
+A few bug fixes.
+
+* Fixed bug with `nil` multi-bulk reply lengths that showed up with `BLPOP` timeouts.
+* Only emit `end` once when connection goes away.
+* Fixed bug in `test.js` where driver finished before all tests completed.
+
+## unversioned wasteland
+
+See the git history for what happened before.
View
5 node_modules/redis/examples/auth.js
@@ -0,0 +1,5 @@
+var redis = require("redis"),
+ client = redis.createClient();
+
+// This command is magical. Client stashes the password and will issue on every connect.
+client.auth("somepass");
View
33 node_modules/redis/examples/backpressure_drain.js
@@ -0,0 +1,33 @@
+var redis = require("../index"),
+ client = redis.createClient(null, null, {
+ command_queue_high_water: 5,
+ command_queue_low_water: 1
+ }),
+ remaining_ops = 100000, paused = false;
+
+function op() {
+ if (remaining_ops <= 0) {
+ console.error("Finished.");
+ process.exit(0);
+ }
+
+ remaining_ops--;
+ if (client.hset("test hash", "val " + remaining_ops, remaining_ops) === false) {
+ console.log("Pausing at " + remaining_ops);
+ paused = true;
+ } else {
+ process.nextTick(op);
+ }
+}
+
+client.on("drain", function () {
+ if (paused) {
+ console.log("Resuming at " + remaining_ops);
+ paused = false;
+ process.nextTick(op);
+ } else {
+ console.log("Got drain while not paused at " + remaining_ops);
+ }
+});
+
+op();
View
9 node_modules/redis/examples/eval.js
@@ -0,0 +1,9 @@
+var redis = require("./index"),
+ client = redis.createClient();
+
+redis.debug_mode = true;
+
+client.eval("return 100.5", 0, function (err, res) {
+ console.dir(err);
+ console.dir(res);
+});
View
24 node_modules/redis/examples/extend.js
@@ -0,0 +1,24 @@
+var redis = require("redis"),
+ client = redis.createClient();
+
+// Extend the RedisClient prototype to add a custom method
+// This one converts the results from "INFO" into a JavaScript Object
+
+redis.RedisClient.prototype.parse_info = function (callback) {
+ this.info(function (err, res) {
+ var lines = res.toString().split("\r\n").sort();
+ var obj = {};
+ lines.forEach(function (line) {
+ var parts = line.split(':');
+ if (parts[1]) {
+ obj[parts[0]] = parts[1];
+ }
+ });
+ callback(obj)
+ });
+};
+
+client.parse_info(function (info) {
+ console.dir(info);
+ client.quit();
+});
View
32 node_modules/redis/examples/file.js
@@ -0,0 +1,32 @@
+// Read a file from disk, store it in Redis, then read it back from Redis.
+
+var redis = require("redis"),
+ client = redis.createClient(),
+ fs = require("fs"),
+ filename = "kids_in_cart.jpg";
+
+// Get the file I use for testing like this:
+// curl http://ranney.com/kids_in_cart.jpg -o kids_in_cart.jpg
+// or just use your own file.
+
+// Read a file from fs, store it in Redis, get it back from Redis, write it back to fs.
+fs.readFile(filename, function (err, data) {
+ if (err) throw err
+ console.log("Read " + data.length + " bytes from filesystem.");
+
+ client.set(filename, data, redis.print); // set entire file
+ client.get(filename, function (err, reply) { // get entire file
+ if (err) {
+ console.log("Get error: " + err);
+ } else {
+ fs.writeFile("duplicate_" + filename, reply, function (err) {
+ if (err) {
+ console.log("Error on write: " + err)
+ } else {
+ console.log("File written.");
+ }
+ client.end();
+ });
+ }
+ });
+});
View
5 node_modules/redis/examples/mget.js
@@ -0,0 +1,5 @@
+var client = require("redis").createClient();
+
+client.mget(["sessions started", "sessions started", "foo"], function (err, res) {
+ console.dir(res);
+});
View
10 node_modules/redis/examples/monitor.js
@@ -0,0 +1,10 @@
+var client = require("../index").createClient(),
+ util = require("util");
+
+client.monitor(function (err, res) {
+ console.log("Entering monitoring mode.");
+});
+
+client.on("monitor", function (time, args) {
+ console.log(time + ": " + util.inspect(args));
+});
View
46 node_modules/redis/examples/multi.js
@@ -0,0 +1,46 @@
+var redis = require("redis"),
+ client = redis.createClient(), set_size = 20;
+
+client.sadd("bigset", "a member");
+client.sadd("bigset", "another member");
+
+while (set_size > 0) {
+ client.sadd("bigset", "member " + set_size);
+ set_size -= 1;
+}
+
+// multi chain with an individual callback
+client.multi()
+ .scard("bigset")
+ .smembers("bigset")
+ .keys("*", function (err, replies) {
+ client.mget(replies, redis.print);
+ })
+ .dbsize()
+ .exec(function (err, replies) {
+ console.log("MULTI got " + replies.length + " replies");
+ replies.forEach(function (reply, index) {
+ console.log("Reply " + index + ": " + reply.toString());
+ });
+ });
+
+client.mset("incr thing", 100, "incr other thing", 1, redis.print);
+
+// start a separate multi command queue
+var multi = client.multi();
+multi.incr("incr thing", redis.print);
+multi.incr("incr other thing", redis.print);
+
+// runs immediately
+client.get("incr thing", redis.print); // 100
+
+// drains multi queue and runs atomically
+multi.exec(function (err, replies) {
+ console.log(replies); // 101, 2
+});
+
+// you can re-run the same transaction if you like
+multi.exec(function (err, replies) {
+ console.log(replies); // 102, 3
+ client.quit();
+});
View
29 node_modules/redis/examples/multi2.js
@@ -0,0 +1,29 @@
+var redis = require("redis"),
+ client = redis.createClient(), multi;
+
+// start a separate command queue for multi
+multi = client.multi();
+multi.incr("incr thing", redis.print);
+multi.incr("incr other thing", redis.print);
+
+// runs immediately
+client.mset("incr thing", 100, "incr other thing", 1, redis.print);
+
+// drains multi queue and runs atomically
+multi.exec(function (err, replies) {
+ console.log(replies); // 101, 2
+});
+
+// you can re-run the same transaction if you like
+multi.exec(function (err, replies) {
+ console.log(replies); // 102, 3
+ client.quit();
+});
+
+client.multi([
+ ["mget", "multifoo", "multibar", redis.print],
+ ["incr", "multifoo"],
+ ["incr", "multibar"]
+]).exec(function (err, replies) {
+ console.log(replies.toString());
+});
View
33 node_modules/redis/examples/psubscribe.js
@@ -0,0 +1,33 @@
+var redis = require("redis"),
+ client1 = redis.createClient(),
+ client2 = redis.createClient(),
+ client3 = redis.createClient(),
+ client4 = redis.createClient(),
+ msg_count = 0;
+
+redis.debug_mode = false;
+
+client1.on("psubscribe", function (pattern, count) {
+ console.log("client1 psubscribed to " + pattern + ", " + count + " total subscriptions");
+ client2.publish("channeltwo", "Me!");
+ client3.publish("channelthree", "Me too!");
+ client4.publish("channelfour", "And me too!");
+});
+
+client1.on("punsubscribe", function (pattern, count) {
+ console.log("client1 punsubscribed from " + pattern + ", " + count + " total subscriptions");
+ client4.end();
+ client3.end();
+ client2.end();
+ client1.end();
+});
+
+client1.on("pmessage", function (pattern, channel, message) {
+ console.log("("+ pattern +")" + " client1 received message on " + channel + ": " + message);
+ msg_count += 1;
+ if (msg_count === 3) {
+ client1.punsubscribe();
+ }
+});
+
+client1.psubscribe("channel*");
View
41 node_modules/redis/examples/pub_sub.js
@@ -0,0 +1,41 @@
+var redis = require("redis"),
+ client1 = redis.createClient(), msg_count = 0,
+ client2 = redis.createClient();
+
+redis.debug_mode = false;
+
+// Most clients probably don't do much on "subscribe". This example uses it to coordinate things within one program.
+client1.on("subscribe", function (channel, count) {
+ console.log("client1 subscribed to " + channel + ", " + count + " total subscriptions");
+ if (count === 2) {
+ client2.publish("a nice channel", "I am sending a message.");
+ client2.publish("another one", "I am sending a second message.");
+ client2.publish("a nice channel", "I am sending my last message.");
+ }
+});
+
+client1.on("unsubscribe", function (channel, count) {
+ console.log("client1 unsubscribed from " + channel + ", " + count + " total subscriptions");
+ if (count === 0) {
+ client2.end();
+ client1.end();
+ }
+});
+
+client1.on("message", function (channel, message) {
+ console.log("client1 channel " + channel + ": " + message);
+ msg_count += 1;
+ if (msg_count === 3) {
+ client1.unsubscribe();
+ }
+});
+
+client1.on("ready", function () {
+ // if you need auth, do it here
+ client1.incr("did a thing");
+ client1.subscribe("a nice channel", "another one");
+});
+
+client2.on("ready", function () {
+ // if you need auth, do it here
+});
View
24 node_modules/redis/examples/simple.js
@@ -0,0 +1,24 @@
+var redis = require("redis"),
+ client = redis.createClient();
+
+client.on("error", function (err) {
+ console.log("error event - " + client.host + ":" + client.port + " - " + err);
+});
+
+client.set("string key", "string val", redis.print);
+client.hset("hash key", "hashtest 1", "some value", redis.print);
+client.hset(["hash key", "hashtest 2", "some other value"], redis.print);
+client.hkeys("hash key", function (err, replies) {
+ if (err) {
+ return console.error("error response - " + err);
+ }
+
+ console.log(replies.length + " replies:");
+ replies.forEach(function (reply, i) {
+ console.log(" " + i + ": " + reply);
+ });
+});
+
+client.quit(function (err, res) {
+ console.log("Exiting from quit command.");
+});
View
17 node_modules/redis/examples/sort.js
@@ -0,0 +1,17 @@
+var redis = require("redis"),
+ client = redis.createClient();
+
+client.sadd("mylist", 1);
+client.sadd("mylist", 2);
+client.sadd("mylist", 3);
+
+client.set("weight_1", 5);
+client.set("weight_2", 500);
+client.set("weight_3", 1);
+
+client.set("object_1", "foo");
+client.set("object_2", "bar");
+client.set("object_3", "qux");
+
+client.sort("mylist", "by", "weight_*", "get", "object_*", redis.print);
+// Prints Reply: qux,foo,bar
View
15 node_modules/redis/examples/subqueries.js
@@ -0,0 +1,15 @@
+// Sending commands in response to other commands.
+// This example runs "type" against every key in the database
+//
+var client = require("redis").createClient();
+
+client.keys("*", function (err, keys) {
+ keys.forEach(function (key, pos) {
+ client.type(key, function (err, keytype) {
+ console.log(key + " is " + keytype);
+ if (pos === (keys.length - 1)) {
+ client.quit();
+ }
+ });
+ });
+});
View
19 node_modules/redis/examples/subquery.js
@@ -0,0 +1,19 @@
+var client = require("redis").createClient();
+
+function print_results(obj) {
+ console.dir(obj);
+}
+
+// build a map of all keys and their types
+client.keys("*", function (err, all_keys) {
+ var key_types = {};
+
+ all_keys.forEach(function (key, pos) { // use second arg of forEach to get pos
+ client.type(key, function (err, type) {
+ key_types[key] = type;
+ if (pos === all_keys.length - 1) { // callbacks all run in order
+ print_results(key_types);
+ }
+ });
+ });
+});
View
29 node_modules/redis/examples/unix_socket.js
@@ -0,0 +1,29 @@
+var redis = require("redis"),
+ client = redis.createClient("/tmp/redis.sock"),
+ profiler = require("v8-profiler");
+
+client.on("connect", function () {
+ console.log("Got Unix socket connection.")
+});
+
+client.on("error", function (err) {
+ console.log(err.message);
+});
+
+client.set("space chars", "space value");
+
+setInterval(function () {
+ client.get("space chars");
+}, 100);
+
+function done() {
+ client.info(function (err, reply) {
+ console.log(reply.toString());
+ client.quit();
+ });
+}
+
+setTimeout(function () {
+ console.log("Taking snapshot.");
+ var snap = profiler.takeSnapshot();
+}, 5000);
View
31 node_modules/redis/examples/web_server.js
@@ -0,0 +1,31 @@
+// A simple web server that generates dyanmic content based on responses from Redis
+
+var http = require("http"), server,
+ redis_client = require("redis").createClient();
+
+server = http.createServer(function (request, response) {
+ response.writeHead(200, {
+ "Content-Type": "text/plain"
+ });
+
+ var redis_info, total_requests;
+
+ redis_client.info(function (err, reply) {
+ redis_info = reply; // stash response in outer scope
+ });
+ redis_client.incr("requests", function (err, reply) {
+ total_requests = reply; // stash response in outer scope
+ });
+ redis_client.hincrby("ip", request.connection.remoteAddress, 1);
+ redis_client.hgetall("ip", function (err, reply) {
+ // This is the last reply, so all of the previous replies must have completed already
+ response.write("This page was generated after talking to redis.\n\n" +
+ "Redis info:\n" + redis_info + "\n" +
+ "Total requests: " + total_requests + "\n\n" +
+ "IP count: \n");
+ Object.keys(reply).forEach(function (ip) {
+ response.write(" " + ip + ": " + reply[ip] + "\n");
+ });
+ response.end();
+ });
+}).listen(80);
View
39 node_modules/redis/generate_commands.js
@@ -0,0 +1,39 @@
+var http = require("http"),
+ fs = require("fs");
+
+function prettyCurrentTime() {
+ var date = new Date();
+ return date.toLocaleString();
+}
+
+function write_file(commands, path) {
+ var file_contents, out_commands;
+
+ console.log("Writing " + Object.keys(commands).length + " commands to " + path);
+
+ file_contents = "// This file was generated by ./generate_commands.js on " + prettyCurrentTime() + "\n";
+
+ out_commands = Object.keys(commands).map(function (key) {
+ return key.toLowerCase();
+ });
+
+ file_contents += "module.exports = " + JSON.stringify(out_commands, null, " ") + ";\n";
+
+ fs.writeFile(path, file_contents);
+}
+
+http.get({host: "redis.io", path: "/commands.json"}, function (res) {
+ var body = "";
+
+ console.log("Response from redis.io/commands.json: " + res.statusCode);
+
+ res.on('data', function (chunk) {
+ body += chunk;
+ });
+
+ res.on('end', function () {
+ write_file(JSON.parse(body), "lib/commands.js");
+ });
+}).on('error', function (e) {
+ console.log("Error fetching command list from redis.io: " + e.message);
+});
View
1,030 node_modules/redis/index.js
@@ -0,0 +1,1030 @@
+/*global Buffer require exports console setTimeout */
+
+var net = require("net"),
+ util = require("./lib/util"),
+ Queue = require("./lib/queue"),
+ to_array = require("./lib/to_array"),
+ events = require("events"),
+ parsers = [], commands,
+ connection_id = 0,
+ default_port = 6379,
+ default_host = "127.0.0.1";
+
+// can set this to true to enable for all connections
+exports.debug_mode = false;
+
+// hiredis might not be installed
+try {
+ require("./lib/parser/hiredis");
+ parsers.push(require("./lib/parser/hiredis"));
+} catch (err) {
+ if (exports.debug_mode) {
+ console.warn("hiredis parser not installed.");
+ }
+}
+
+parsers.push(require("./lib/parser/javascript"));
+
+function RedisClient(stream, options) {
+ this.stream = stream;
+ this.options = options = options || {};
+
+ this.connection_id = ++connection_id;
+ this.connected = false;
+ this.ready = false;
+ this.connections = 0;
+ if (this.options.socket_nodelay === undefined) {
+ this.options.socket_nodelay = true;
+ }
+ this.should_buffer = false;
+ this.command_queue_high_water = this.options.command_queue_high_water || 1000;
+ this.command_queue_low_water = this.options.command_queue_low_water || 0;
+ this.max_attempts = null;
+ if (options.max_attempts && !isNaN(options.max_attempts) && options.max_attempts > 0) {
+ this.max_attempts = +options.max_attempts;
+ }
+ 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.commands_sent = 0;
+ this.connect_timeout = false;
+ if (options.connect_timeout && !isNaN(options.connect_timeout) && options.connect_timeout > 0) {
+ this.connect_timeout = +options.connect_timeout;
+ }
+ this.initialize_retry_vars();
+ this.pub_sub_mode = false;
+ this.subscription_set = {};
+ this.monitoring = false;
+ this.closing = false;
+ this.server_info = {};
+ this.auth_pass = null;
+ this.parser_module = null;
+ this.selected_db = null; // save the selected db here, used when reconnecting
+
+ var self = this;
+
+ this.stream.on("connect", function () {
+ self.on_connect();
+ });
+
+ this.stream.on("data", function (buffer_from_socket) {
+ self.on_data(buffer_from_socket);
+ });
+
+ this.stream.on("error", function (msg) {
+ self.on_error(msg.message);
+ });
+
+ this.stream.on("close", function () {
+ self.connection_gone("close");
+ });
+
+ this.stream.on("end", function () {
+ self.connection_gone("end");
+ });
+
+ this.stream.on("drain", function () {
+ self.should_buffer = false;
+ self.emit("drain");
+ });
+
+ events.EventEmitter.call(this);
+}
+util.inherits(RedisClient, events.EventEmitter);
+exports.RedisClient = RedisClient;
+
+RedisClient.prototype.initialize_retry_vars = function () {
+ this.retry_timer = null;
+ this.retry_totaltime = 0;
+ this.retry_delay = 150;
+ this.retry_backoff = 1.7;
+ this.attempts = 1;
+};
+
+// flush offline_queue and command_queue, erroring any items with a callback first
+RedisClient.prototype.flush_and_error = function (message) {
+ var command_obj;
+ while (this.offline_queue.length > 0) {
+ command_obj = this.offline_queue.shift();
+ if (typeof command_obj.callback === "function") {
+ command_obj.callback(message);
+ }
+ }
+ this.offline_queue = new Queue();
+
+ while (this.command_queue.length > 0) {
+ command_obj = this.command_queue.shift();
+ if (typeof command_obj.callback === "function") {
+ command_obj.callback(message);
+ }
+ }
+ this.command_queue = new Queue();
+};
+
+RedisClient.prototype.on_error = function (msg) {
+ var message = "Redis connection to " + this.host + ":" + this.port + " failed - " + msg,
+ self = this, command_obj;
+
+ if (this.closing) {
+ return;
+ }
+
+ if (exports.debug_mode) {
+ console.warn(message);
+ }
+
+ this.flush_and_error(message);
+
+ this.connected = false;
+ this.ready = false;
+
+ this.emit("error", new Error(message));
+ // "error" events get turned into exceptions if they aren't listened for. If the user handled this error
+ // then we should try to reconnect.
+ this.connection_gone("error");
+};
+
+RedisClient.prototype.do_auth = function () {
+ var self = this;
+
+ if (exports.debug_mode) {
+ console.log("Sending auth to " + self.host + ":" + self.port + " id " + self.connection_id);
+ }
+ self.send_anyway = true;
+ self.send_command("auth", [this.auth_pass], function (err, res) {
+ if (err) {
+ if (err.toString().match("LOADING")) {
+ // if redis is still loading the db, it will not authenticate and everything else will fail
+ console.log("Redis still loading, trying to authenticate later");
+ setTimeout(function () {
+ self.do_auth();
+ }, 2000); // TODO - magic number alert
+ return;
+ } else {
+ return self.emit("error", new Error("Auth error: " + err.message));
+ }
+ }
+ if (res.toString() !== "OK") {
+ return self.emit("error", new Error("Auth failed: " + res.toString()));
+ }
+ if (exports.debug_mode) {
+ console.log("Auth succeeded " + self.host + ":" + self.port + " id " + self.connection_id);
+ }
+ if (self.auth_callback) {
+ self.auth_callback(err, res);
+ self.auth_callback = null;
+ }
+
+ // now we are really connected
+ self.emit("connect");
+ if (self.options.no_ready_check) {
+ self.on_ready();
+ } else {
+ self.ready_check();
+ }
+ });
+ self.send_anyway = false;
+};
+
+RedisClient.prototype.on_connect = function () {
+ if (exports.debug_mode) {
+ console.log("Stream connected " + this.host + ":" + this.port + " id " + this.connection_id);
+ }
+ var self = this;
+
+ this.connected = true;
+ this.ready = false;
+ this.attempts = 0;
+ this.connections += 1;
+ this.command_queue = new Queue();
+ this.emitted_end = false;
+ this.initialize_retry_vars();
+ if (this.options.socket_nodelay) {
+ this.stream.setNoDelay();
+ }
+ this.stream.setTimeout(0);
+
+ this.init_parser();
+
+ if (this.auth_pass) {
+ this.do_auth();
+ } else {
+ this.emit("connect");
+
+ if (this.options.no_ready_check) {
+ this.on_ready();
+ } else {
+ this.ready_check();
+ }
+ }
+};
+
+RedisClient.prototype.init_parser = function () {
+ var self = this;
+
+ if (this.options.parser) {
+ if (! parsers.some(function (parser) {
+ if (parser.name === self.options.parser) {
+ self.parser_module = parser;
+ if (exports.debug_mode) {
+ console.log("Using parser module: " + self.parser_module.name);
+ }
+ return true;
+ }
+ })) {
+ throw new Error("Couldn't find named parser " + self.options.parser + " on this system");
+ }
+ } else {
+ if (exports.debug_mode) {
+ console.log("Using default parser module: " + parsers[0].name);
+ }
+ this.parser_module = parsers[0];
+ }
+
+ this.parser_module.debug_mode = exports.debug_mode;
+
+ // return_buffers sends back Buffers from parser to callback. detect_buffers sends back Buffers from parser, but
+ // converts to Strings if the input arguments are not Buffers.
+ this.reply_parser = new this.parser_module.Parser({
+ return_buffers: self.options.return_buffers || self.options.detect_buffers || false
+ });
+
+ // "reply error" is an error sent back by Redis
+ this.reply_parser.on("reply error", function (reply) {
+ self.return_error(new Error(reply));
+ });
+ this.reply_parser.on("reply", function (reply) {
+ self.return_reply(reply);
+ });
+ // "error" is bad. Somehow the parser got confused. It'll try to reset and continue.
+ this.reply_parser.on("error", function (err) {
+ self.emit("error", new Error("Redis reply parser error: " + err.stack));
+ });
+};
+
+RedisClient.prototype.on_ready = function () {
+ var self = this;
+
+ this.ready = true;
+
+ // magically restore any modal commands from a previous connection
+ if (this.selected_db !== null) {
+ this.send_command('select', [this.selected_db]);
+ }
+ if (this.pub_sub_mode === true) {
+ Object.keys(this.subscription_set).forEach(function (key) {
+ var parts = key.split(" ");
+ if (exports.debug_mode) {
+ console.warn("sending pub/sub on_ready " + parts[0] + ", " + parts[1]);
+ }
+ self.send_command(parts[0], [parts[1]]);
+ });
+ } else if (this.monitoring) {
+ this.send_command("monitor");
+ } else {
+ this.send_offline_queue();
+ }
+ this.emit("ready");
+};
+
+RedisClient.prototype.on_info_cmd = function (err, res) {
+ var self = this, obj = {}, lines, retry_time;
+
+ if (err) {
+ return self.emit("error", new Error("Ready check failed: " + err.message));
+ }
+
+ lines = res.toString().split("\r\n");
+
+ lines.forEach(function (line) {
+ var parts = line.split(':');
+ if (parts[1]) {
+ obj[parts[0]] = parts[1];
+ }
+ });
+
+ obj.versions = [];
+ obj.redis_version.split('.').forEach(function (num) {
+ obj.versions.push(+num);
+ });
+
+ // expose info key/vals to users
+ this.server_info = obj;
+
+ if (!obj.loading || (obj.loading && obj.loading === "0")) {
+ if (exports.debug_mode) {
+ console.log("Redis server ready.");
+ }
+ this.on_ready();
+ } else {
+ retry_time = obj.loading_eta_seconds * 1000;
+ if (retry_time > 1000) {
+ retry_time = 1000;
+ }
+ if (exports.debug_mode) {
+ console.log("Redis server still loading, trying again in " + retry_time);
+ }
+ setTimeout(function () {
+ self.ready_check();
+ }, retry_time);
+ }
+};
+
+RedisClient.prototype.ready_check = function () {
+ var self = this;
+
+ if (exports.debug_mode) {
+ console.log("checking server ready state...");
+ }
+
+ this.send_anyway = true; // secret flag to send_command to send something even if not "ready"
+ this.info(function (err, res) {
+ self.on_info_cmd(err, res);
+ });
+ this.send_anyway = false;
+};
+
+RedisClient.prototype.send_offline_queue = function () {
+ var command_obj, buffered_writes = 0;
+
+ while (this.offline_queue.length > 0) {
+ command_obj = this.offline_queue.shift();
+ if (exports.debug_mode) {
+ console.log("Sending offline command: " + command_obj.command);
+ }
+ buffered_writes += !this.send_command(command_obj.command, command_obj.args, command_obj.callback);
+ }
+ this.offline_queue = new Queue();
+ // Even though items were shifted off, Queue backing store still uses memory until next add, so just get a new Queue
+
+ if (!buffered_writes) {
+ this.should_buffer = false;
+ this.emit("drain");
+ }
+};
+
+RedisClient.prototype.connection_gone = function (why) {
+ var self = this, message;
+
+ // If a retry is already in progress, just let that happen
+ if (this.retry_timer) {
+ return;
+ }
+
+ if (exports.debug_mode) {
+ console.warn("Redis connection is gone from " + why + " event.");
+ }
+ this.connected = false;
+ this.ready = false;
+
+ // since we are collapsing end and close, users don't expect to be called twice
+ if (! this.emitted_end) {
+ this.emit("end");
+ this.emitted_end = true;
+ }
+
+ this.flush_and_error("Redis connection gone from " + why + " event.");
+
+ // If this is a requested shutdown, then don't retry
+ if (this.closing) {
+ this.retry_timer = null;
+ if (exports.debug_mode) {
+ console.warn("connection ended from quit command, not retrying.");
+ }
+ return;
+ }
+
+ this.retry_delay = Math.floor(this.retry_delay * this.retry_backoff);
+
+ if (exports.debug_mode) {
+ console.log("Retry connection in " + this.current_retry_delay + " ms");
+ }
+
+ if (this.max_attempts && this.attempts >= this.max_attempts) {
+ this.retry_timer = null;
+ // TODO - some people need a "Redis is Broken mode" for future commands that errors immediately, and others
+ // want the program to exit. Right now, we just log, which doesn't really help in either case.
+ console.error("node_redis: Couldn't get Redis connection after " + this.max_attempts + " attempts.");
+ return;
+ }
+
+ this.attempts += 1;
+ this.emit("reconnecting", {
+ delay: self.retry_delay,
+ attempt: self.attempts
+ });
+ this.retry_timer = setTimeout(function () {
+ if (exports.debug_mode) {
+ console.log("Retrying connection...");
+ }
+
+ self.retry_totaltime += self.current_retry_delay;
+
+ if (self.connect_timeout && self.retry_totaltime >= self.connect_timeout) {
+ self.retry_timer = null;
+ // TODO - engage Redis is Broken mode for future commands, or whatever
+ console.error("node_redis: Couldn't get Redis connection after " + self.retry_totaltime + "ms.");
+ return;
+ }
+
+ self.stream.connect(self.port, self.host);
+ self.retry_timer = null;
+ }, this.retry_delay);
+};
+
+RedisClient.prototype.on_data = function (data) {
+ if (exports.debug_mode) {
+ console.log("net read " + this.host + ":" + this.port + " id " + this.connection_id + ": " + data.toString());
+ }
+
+ try {
+ this.reply_parser.execute(data);
+ } catch (err) {
+ // This is an unexpected parser problem, an exception that came from the parser code itself.
+ // Parser should emit "error" events if it notices things are out of whack.
+ // Callbacks that throw exceptions will land in return_reply(), below.
+ // TODO - it might be nice to have a different "error" event for different types of errors
+ this.emit("error", err);
+ }
+};
+
+RedisClient.prototype.return_error = function (err) {
+ var command_obj = this.command_queue.shift(), queue_len = this.command_queue.getLength();
+
+ if (this.pub_sub_mode === false && queue_len === 0) {
+ this.emit("idle");
+ this.command_queue = new Queue();
+ }
+ if (this.should_buffer && queue_len <= this.command_queue_low_water) {
+ this.emit("drain");
+ this.should_buffer = false;
+ }
+
+ if (command_obj && typeof command_obj.callback === "function") {
+ try {
+ command_obj.callback(err);
+ } catch (callback_err) {
+ // if a callback throws an exception, re-throw it on a new stack so the parser can keep going
+ process.nextTick(function () {
+ throw callback_err;
+ });
+ }
+ } else {
+ console.log("node_redis: no callback to send error: " + err.message);
+ // this will probably not make it anywhere useful, but we might as well throw
+ process.nextTick(function () {
+ throw err;
+ });
+ }
+};
+
+// if a callback throws an exception, re-throw it on a new stack so the parser can keep going.
+// put this try/catch in its own function because V8 doesn't optimize this well yet.
+function try_callback(callback, reply) {
+ try {
+ callback(null, reply);
+ } catch (err) {
+ process.nextTick(function () {
+ throw err;
+ });
+ }
+}
+
+// hgetall converts its replies to an Object. If the reply is empty, null is returned.
+function reply_to_object(reply) {
+ var obj = {}, j, jl, key, val;
+
+ if (reply.length === 0) {
+ return null;
+ }
+
+ for (j = 0, jl = reply.length; j < jl; j += 2) {
+ key = reply[j].toString();
+ val = reply[j + 1];
+ obj[key] = val;
+ }
+
+ return obj;
+}
+
+function reply_to_strings(reply) {
+ var i;
+
+ if (Buffer.isBuffer(reply)) {
+ return reply.toString();
+ }
+
+ if (Array.isArray(reply)) {
+ for (i = 0; i < reply.length; i++) {
+ reply[i] = reply[i].toString();
+ }
+ return reply;
+ }
+
+ return reply;
+}
+
+RedisClient.prototype.return_reply = function (reply) {
+ var command_obj, obj, i, len, type, timestamp, argindex, args, queue_len;
+
+ queue_len = this.command_queue.getLength();
+
+ if (this.pub_sub_mode === false && queue_len === 0) {
+ this.emit("idle");
+ this.command_queue = new Queue(); // explicitly reclaim storage from old Queue
+ }
+ if (this.should_buffer && queue_len <= this.command_queue_low_water) {
+ this.emit("drain");
+ this.should_buffer = false;
+ }
+
+ command_obj = this.command_queue.shift();
+
+ if (command_obj && !command_obj.sub_command) {
+ if (typeof command_obj.callback === "function") {
+ if (this.options.detect_buffers && command_obj.buffer_args === false) {
+ // If detect_buffers option was specified, then the reply from the parser will be Buffers.
+ // If this command did not use Buffer arguments, then convert the reply to Strings here.
+ reply = reply_to_strings(reply);
+ }
+
+ // TODO - confusing and error-prone that hgetall is special cased in two places
+ if (reply && 'hgetall' === command_obj.command.toLowerCase()) {
+ reply = reply_to_object(reply);
+ }
+
+ try_callback(command_obj.callback, reply);
+ } else if (exports.debug_mode) {
+ console.log("no callback for reply: " + (reply && reply.toString && reply.toString()));
+ }
+ } else if (this.pub_sub_mode || (command_obj && command_obj.sub_command)) {
+ if (Array.isArray(reply)) {
+ type = reply[0].toString();
+
+ if (type === "message") {
+ this.emit("message", reply[1].toString(), reply[2]); // channel, message
+ } else if (type === "pmessage") {
+ this.emit("pmessage", reply[1].toString(), reply[2].toString(), reply[3]); // pattern, channel, message
+ } else if (type === "subscribe" || type === "unsubscribe" || type === "psubscribe" || type === "punsubscribe") {
+ if (reply[2] === 0) {
+ this.pub_sub_mode = false;
+ if (this.debug_mode) {
+ console.log("All subscriptions removed, exiting pub/sub mode");
+ }
+ } else {
+ this.pub_sub_mode = true;
+ }
+ // subscribe commands take an optional callback and also emit an event, but only the first response is included in the callback
+ // TODO - document this or fix it so it works in a more obvious way
+ if (command_obj && typeof command_obj.callback === "function") {
+ try_callback(command_obj.callback, reply[1].toString());
+ }
+ this.emit(type, reply[1].toString(), reply[2]); // channel, count
+ } else {
+ throw new Error("subscriptions are active but got unknown reply type " + type);
+ }
+ } else if (! this.closing) {
+ throw new Error("subscriptions are active but got an invalid reply: " + reply);
+ }
+ } else if (this.monitoring) {
+ len = reply.indexOf(" ");
+ timestamp = reply.slice(0, len);
+ argindex = reply.indexOf('"');
+ args = reply.slice(argindex + 1, -1).split('" "').map(function (elem) {
+ return elem.replace(/\\"/g, '"');
+ });
+ this.emit("monitor", timestamp, args);
+ } else {
+ throw new Error("node_redis command queue state error. If you can reproduce this, please report it.");
+ }
+};
+
+// This Command constructor is ever so slightly faster than using an object literal, but more importantly, using
+// a named constructor helps it show up meaningfully in the V8 CPU profiler and in heap snapshots.
+function Command(command, args, sub_command, buffer_args, callback) {
+ this.command = command;
+ this.args = args;
+ this.sub_command = sub_command;
+ this.buffer_args = buffer_args;
+ this.callback = callback;
+}
+
+RedisClient.prototype.send_command = function (command, args, callback) {
+ var arg, this_args, command_obj, i, il, elem_count, buffer_args, stream = this.stream, command_str = "", buffered_writes = 0, last_arg_type;
+
+ if (typeof command !== "string") {
+ throw new Error("First argument to send_command must be the command name string, not " + typeof command);
+ }
+
+ if (Array.isArray(args)) {
+ if (typeof callback === "function") {
+ // probably the fastest way:
+ // client.command([arg1, arg2], cb); (straight passthrough)
+ // send_command(command, [arg1, arg2], cb);
+ } else if (! callback) {
+ // most people find this variable argument length form more convenient, but it uses arguments, which is slower
+ // client.command(arg1, arg2, cb); (wraps up arguments into an array)
+ // send_command(command, [arg1, arg2, cb]);
+ // client.command(arg1, arg2); (callback is optional)
+ // send_command(command, [arg1, arg2]);
+ // client.command(arg1, arg2, undefined); (callback is undefined)
+ // send_command(command, [arg1, arg2, undefined]);
+ last_arg_type = typeof args[args.length - 1];
+ if (last_arg_type === "function" || last_arg_type === "undefined") {
+ callback = args.pop();
+ }
+ } else {
+ throw new Error("send_command: last argument must be a callback or undefined");
+ }
+ } else {
+ throw new Error("send_command: second argument must be an array");
+ }
+
+ // if the last argument is an array, expand it out. This allows commands like this:
+ // client.command(arg1, [arg2, arg3, arg4], cb);
+ // and converts to:
+ // client.command(arg1, arg2, arg3, arg4, cb);
+ // which is convenient for some things like sadd
+ if (args.length > 0 && Array.isArray(args[args.length - 1])) {
+ args = args.slice(0, -1).concat(args[args.length - 1]);
+ }
+
+ buffer_args = false;
+ for (i = 0, il = args.length, arg; i < il; i += 1) {
+ if (Buffer.isBuffer(args[i])) {
+ buffer_args = true;
+ }
+ }
+
+ command_obj = new Command(command, args, false, buffer_args, callback);
+
+ if ((!this.ready && !this.send_anyway) || !stream.writable) {
+ if (exports.debug_mode) {
+ if (!stream.writable) {
+ console.log("send command: stream is not writeable.");
+ }
+
+ console.log("Queueing " + command + " for next server connection.");
+ }
+ this.offline_queue.push(command_obj);
+ this.should_buffer = true;
+ return false;
+ }
+
+ if (command === "subscribe" || command === "psubscribe" || command === "unsubscribe" || command === "punsubscribe") {
+ this.pub_sub_command(command_obj);
+ } else if (command === "monitor") {
+ this.monitoring = true;
+ } else if (command === "quit") {
+ this.closing = true;
+ } else if (this.pub_sub_mode === true) {
+ throw new Error("Connection in pub/sub mode, only pub/sub commands may be used");
+ }
+ this.command_queue.push(command_obj);
+ this.commands_sent += 1;
+
+ elem_count = args.length + 1;
+
+ // Always use "Multi bulk commands", but if passed any Buffer args, then do multiple writes, one for each arg.
+ // This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer.
+
+ command_str = "*" + elem_count + "\r\n$" + command.length + "\r\n" + command + "\r\n";
+
+ if (! buffer_args) { // Build up a string and send entire command in one write
+ for (i = 0, il = args.length, arg; i < il; i += 1) {
+ arg = args[i];
+ if (typeof arg !== "string") {
+ arg = String(arg);
+ }
+ command_str += "$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n";
+ }
+ if (exports.debug_mode) {
+ console.log("send " + this.host + ":" + this.port + " id " + this.connection_id + ": " + command_str);
+ }
+ buffered_writes += !stream.write(command_str);
+ } else {
+ if (exports.debug_mode) {
+ console.log("send command (" + command_str + ") has Buffer arguments");
+ }
+ buffered_writes += !stream.write(command_str);
+
+ for (i = 0, il = args.length, arg; i < il; i += 1) {
+ arg = args[i];
+ if (!(Buffer.isBuffer(arg) || arg instanceof String)) {
+ arg = String(arg);
+ }
+
+ if (Buffer.isBuffer(arg)) {
+ if (arg.length === 0) {
+ if (exports.debug_mode) {
+ console.log("send_command: using empty string for 0 length buffer");
+ }
+ buffered_writes += !stream.write("$0\r\n\r\n");
+ } else {
+ buffered_writes += !stream.write("$" + arg.length + "\r\n");
+ buffered_writes += !stream.write(arg);
+ buffered_writes += !stream.write("\r\n");
+ if (exports.debug_mode) {
+ console.log("send_command: buffer send " + arg.length + " bytes");
+ }
+ }
+ } else {
+ if (exports.debug_mode) {
+ console.log("send_command: string send " + Buffer.byteLength(arg) + " bytes: " + arg);
+ }
+ buffered_writes += !stream.write("$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n");
+ }
+ }
+ }
+ if (exports.debug_mode) {
+ console.log("send_command buffered_writes: " + buffered_writes, " should_buffer: " + this.should_buffer);
+ }
+ if (buffered_writes || this.command_queue.getLength() >= this.command_queue_high_water) {
+ this.should_buffer = true;
+ }
+ return !this.should_buffer;
+};
+
+RedisClient.prototype.pub_sub_command = function (command_obj) {
+ var i, key, command, args;
+
+ if (this.pub_sub_mode === false && exports.debug_mode) {
+ console.log("Entering pub/sub mode from " + command_obj.command);
+ }
+ this.pub_sub_mode = true;
+ command_obj.sub_command = true;
+
+ command = command_obj.command;
+ args = command_obj.args;
+ if (command === "subscribe" || command === "psubscribe") {
+ if (command === "subscribe") {
+ key = "sub";
+ } else {
+ key = "psub";
+ }
+ for (i = 0; i < args.length; i++) {
+ this.subscription_set[key + " " + args[i]] = true;
+ }
+ } else {
+ if (command === "unsubscribe") {
+ key = "sub";
+ } else {
+ key = "psub";
+ }
+ for (i = 0; i < args.length; i++) {
+ delete this.subscription_set[key + " " + args[i]];
+ }
+ }
+};
+
+RedisClient.prototype.end = function () {
+ this.stream._events = {};
+ this.connected = false;
+ this.ready = false;
+ return this.stream.end();
+};
+
+function Multi(client, args) {
+ this.client = client;
+ this.queue = [["MULTI"]];
+ if (Array.isArray(args)) {
+ this.queue = this.queue.concat(args);
+ }
+}
+
+exports.Multi = Multi;
+
+// take 2 arrays and return the union of their elements
+function set_union(seta, setb) {
+ var obj = {};
+
+ seta.forEach(function (val) {
+ obj[val] = true;
+ });
+ setb.forEach(function (val) {
+ obj[val] = true;
+ });
+ return Object.keys(obj);
+}
+
+// This static list of commands is updated from time to time. ./lib/commands.js can be updated with generate_commands.js
+commands = set_union(["get", "set"