| 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"]); |
| |
| If the key is missing, reply will be null (probably): |
| |
| client.get("missingkey", function(err, reply) { |
| // reply is null when the key is missing |
| console.log(reply); |
| }); |
| |
| 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. |
| * `enable_offline_queue`: defaults to `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 execute immediately |
| with an error, or an error will be thrown if no callback is specified. |
| * `retry_max_delay`: defaults to `null`. By default every time the client tries to connect and fails time before |
| reconnection (delay) almost doubles. This delay normally grows infinitely, but setting `retry_max_delay` limits delay |
| to maximum value, provided in milliseconds. |
| * `connect_timeout` defaults to `false`. By default client will try reconnecting until connected. Setting `connect_timeout` |
| limits total time for client to reconnect. Value is provided in milliseconds and is counted once the disconnect occured. |
| * `max_attempts` defaults to `null`. By default client will try reconnecting until connected. Setting `max_attempts` |
| limits total amount of reconnects. |
| * `auth_pass` defaults to `null`. By default client will try connecting without auth. If set, client will run redis auth command on connect. |
| |
| ```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. |
| |
| ## client.unref() |
| |
| Call `unref()` on the underlying socket connection to the Redis server, allowing the program to exit once no more commands are pending. |
| |
| This is an **experimental** feature, and only supports a subset of the Redis protocol. Any commands where client state is saved on the Redis server, e.g. `*SUBSCRIBE` or the blocking `BL*` commands will *NOT* work with `.unref()`. |
| |
| ```js |
| var redis = require("redis") |
| var client = redis.createClient() |
| |
| /* |
| Calling unref() will allow this program to exit immediately after the get command finishes. Otherwise the client would hang as long as the client-server connection is alive. |
| */ |
| client.unref() |
| client.get("foo", function (err, value){ |
| if (err) throw(err) |
| console.log(value) |
| }) |
| ``` |
| |
| ## 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", // NOTE: key and value will be coerced to strings |
| "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 a "subscriber" 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 subscriber mode, just open another connection. |
| |
| ## Subscriber 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 subscriber mode and no more subscriber 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 subscriber mode and no more subscriber 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()); |
| }); |
| }); |
| ``` |
| |
| ### Multi.exec( callback ) |
| |
| `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. |
| |
| The `callback` of `.exec()` will get invoked with two arguments: |
| |
| * `err` **type:** `null | Array` err is either null or an array of Error Objects corresponding the the sequence the commands where chained. The last item of the array will always be an `EXECABORT` type of error originating from the `.exec()` itself. |
| * `results` **type:** `null | Array` results is an array of responses corresponding the the sequence the commands where chained. |
| |
| 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. |
| |
| ## Multi-word commands |
| |
| To execute redis multi-word commands like `SCRIPT LOAD` or `CLIENT LIST` pass |
| the second word as first parameter: |
| |
| client.script('load', 'return 1'); |
| client.multi().script('load', 'return 1').exec(...); |
| client.multi([['script', 'load', 'return 1']]).exec(...); |
| |
| ## 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. |
| |
| ### Commands with Optional and Keyword arguments |
| |
| This applies to anything that uses an optional `[WITHSCORES]` or `[LIMIT offset count]` in the [redis.io/commands](http://redis.io/commands) documentation. |
| |
| Example: |
| ```js |
| var args = [ 'myzset', 1, 'one', 2, 'two', 3, 'three', 99, 'ninety-nine' ]; |
| client.zadd(args, function (err, response) { |
| if (err) throw err; |
| console.log('added '+response+' items.'); |
| |
| // -Infinity and +Infinity also work |
| var args1 = [ 'myzset', '+inf', '-inf' ]; |
| client.zrevrangebyscore(args1, function (err, response) { |
| if (err) throw err; |
| console.log('example1', response); |
| // write your code here |
| }); |
| |
| var max = 3, min = 1, offset = 1, count = 2; |
| var args2 = [ 'myzset', max, min, 'WITHSCORES', 'LIMIT', offset, count ]; |
| client.zrevrangebyscore(args2, function (err, response) { |
| if (err) throw err; |
| console.log('example2', response); |
| // write your code here |
| }); |
| }); |
| ``` |
| |
| ## 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. |
| |
| ## How to Contribute |
| - open a pull request and then wait for feedback (if |
| [DTrejo](http://github.com/dtrejo) does not get back to you within 2 days, |
| comment again with indignation!) |
| |
| ## Contributors |
| Some people have have added features and fixed bugs in `node_redis` other than me. |
| |
| Ordered by date of first contribution. |
| [Auto-generated](http://github.com/dtrejo/node-authors) on Wed Jul 25 2012 19:14:59 GMT-0700 (PDT). |
| |
| - [Matt Ranney aka `mranney`](https://github.com/mranney) |
| - [Tim-Smart aka `tim-smart`](https://github.com/tim-smart) |
| - [Tj Holowaychuk aka `visionmedia`](https://github.com/visionmedia) |
| - [rick aka `technoweenie`](https://github.com/technoweenie) |
| - [Orion Henry aka `orionz`](https://github.com/orionz) |
| - [Aivo Paas aka `aivopaas`](https://github.com/aivopaas) |
| - [Hank Sims aka `hanksims`](https://github.com/hanksims) |
| - [Paul Carey aka `paulcarey`](https://github.com/paulcarey) |
| - [Pieter Noordhuis aka `pietern`](https://github.com/pietern) |
| - [nithesh aka `nithesh`](https://github.com/nithesh) |
| - [Andy Ray aka `andy2ray`](https://github.com/andy2ray) |
| - [unknown aka `unknowdna`](https://github.com/unknowdna) |
| - [Dave Hoover aka `redsquirrel`](https://github.com/redsquirrel) |
| - [Vladimir Dronnikov aka `dvv`](https://github.com/dvv) |
| - [Umair Siddique aka `umairsiddique`](https://github.com/umairsiddique) |
| - [Louis-Philippe Perron aka `lp`](https://github.com/lp) |
| - [Mark Dawson aka `markdaws`](https://github.com/markdaws) |
| - [Ian Babrou aka `bobrik`](https://github.com/bobrik) |
| - [Felix Geisendörfer aka `felixge`](https://github.com/felixge) |
| - [Jean-Hugues Pinson aka `undefined`](https://github.com/undefined) |
| - [Maksim Lin aka `maks`](https://github.com/maks) |
| - [Owen Smith aka `orls`](https://github.com/orls) |
| - [Zachary Scott aka `zzak`](https://github.com/zzak) |
| - [TEHEK Firefox aka `TEHEK`](https://github.com/TEHEK) |
| - [Isaac Z. Schlueter aka `isaacs`](https://github.com/isaacs) |
| - [David Trejo aka `DTrejo`](https://github.com/DTrejo) |
| - [Brian Noguchi aka `bnoguchi`](https://github.com/bnoguchi) |
| - [Philip Tellis aka `bluesmoon`](https://github.com/bluesmoon) |
| - [Marcus Westin aka `marcuswestin2`](https://github.com/marcuswestin2) |
| - [Jed Schmidt aka `jed`](https://github.com/jed) |
| - [Dave Peticolas aka `jdavisp3`](https://github.com/jdavisp3) |
| - [Trae Robrock aka `trobrock`](https://github.com/trobrock) |
| - [Shankar Karuppiah aka `shankar0306`](https://github.com/shankar0306) |
| - [Ignacio Burgueño aka `ignacio`](https://github.com/ignacio) |
| |
| 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. |
| |
|  |