├── .gitignore ├── .travis.yml ├── API.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── benchmark ├── cluster.js ├── fib.js └── index.js ├── check-commands ├── examples ├── cluster-transaction.js ├── demo-generator.js ├── demo-promise.js └── demo.js ├── lib ├── client.js ├── commands.js ├── connection.js ├── index.js ├── queue.js ├── slot.js └── tool.js ├── package-lock.json ├── package.json └── test ├── client2.js ├── cluster.js ├── commands ├── chaos.js ├── client.js ├── connection.js ├── geo.js ├── hash.js ├── hyperloglog.js ├── key.js ├── list.js ├── pubsub.js ├── script.js ├── server.js ├── set.js ├── sorted-set.js ├── string.js └── transaction.js ├── index.js ├── replica.js └── v5.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | debug 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "8" 5 | - "10" 6 | cache: 7 | directories: 8 | - node_modules 9 | services: 10 | - redis-server 11 | sudo: false 12 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | thunk-redis API 2 | ===== 3 | A thunk/promise-based redis client, support all redis features. 4 | 5 | #### redis.createClient([port], [host], [options]) 6 | #### redis.createClient([addressArray], [options]) 7 | 8 | + port: `Number`, default: `6379`; 9 | + host: `String`, default: `'localhost'`; 10 | + options: `Object`, default: `{}`; 11 | 12 | Create a redis client, return the client. 13 | 14 | ```js 15 | var client1 = redis.createClient(); 16 | var client2 = redis.createClient({database: 2}); 17 | var client3 = redis.createClient(6379, {database: 2}); 18 | var client4 = redis.createClient('127.0.0.1:6379', {database: 2}); 19 | var client5 = redis.createClient(6379, '127.0.0.1', {database: 2}); 20 | // connect to 2 nodes 21 | var client6 = redis.createClient([6379, 6380]) 22 | var client7 = redis.createClient(['127.0.0.1:6379', '127.0.0.1:6380']) // IPv4 23 | var client8 = redis.createClient(['[::1]:6379', '[::1]:6380']) // IPv6 24 | ``` 25 | 26 | #### redis.log([...]) 27 | 28 | ```js 29 | var client = redis.createClient(); 30 | client.info()(redis.log); 31 | ``` 32 | 33 | #### redis.calcSlot(str) 34 | 35 | ```js 36 | redis.calcSlot('123456789'); // => 12739 37 | redis.calcSlot(118); // => 13162 38 | ``` 39 | 40 | ## Events 41 | 42 | #### client.on('close', function () {}) 43 | #### client.on('connect', function () {}) 44 | #### client.on('connection', function (connection) {}) 45 | #### client.on('warn', function (error) {}) 46 | #### client.on('error', function (error) {}) 47 | #### client.on('reconnecting', function (message) {}) 48 | 49 | #### client.on('subscribe', function (pattern, n) {}) 50 | #### client.on('unsubscribe', function (pattern, n) {}) 51 | #### client.on('psubscribe', function (pattern, n) {}) 52 | #### client.on('punsubscribe', function (pattern, n) {}) 53 | #### client.on('message', function (channel, message) {}) 54 | #### client.on('pmessage', function (pattern, channel, message) {}) 55 | #### client.on('monitor', function (message) {}) 56 | 57 | ## Others 58 | 59 | 60 | #### client.clientConnect() 61 | #### client.clientEnd() 62 | #### client.clientUnref() 63 | #### client.clientState() 64 | 65 | #### client.evalauto(script, numkeys, key, [key ...], arg, [arg ...]) 66 | 67 | #### client.clientCommands 68 | 69 | ## Commands 70 | 71 | ### Keys 72 | #### client.del(key, [key ...]) | client.del([key1, key2, ...]) 73 | #### client.dump(key) 74 | #### client.exists(key) 75 | #### client.expire(key, seconds) 76 | #### client.expireat(key, timestamp) 77 | #### client.keys(pattern) 78 | #### client.migrate(host, port, key, db, timeout, [COPY], [REPLASE]) 79 | #### client.move(key, db) 80 | #### client.object(subcommand, [key, [key ...]]) 81 | #### client.persist(key) 82 | #### client.pexpire(key, milliseconds) 83 | #### client.pexpireat(key, ms-timestamp) 84 | #### client.pttl(key) 85 | #### client.randomkey() 86 | #### client.rename(key, newkey) 87 | #### client.renamenx(key, newkey) 88 | #### client.restore(key, ttl, serialized-value) 89 | #### client.sort(key, [BY pattern], [LIMIT offset count], [GET pattern [GET pattern ...]], [ASC | DESC], [ALPHA], [STORE destination]) 90 | #### client.ttl(key) 91 | #### client.type(key) 92 | #### client.scan(cursor, [MATCH pattern], [COUNT count]) 93 | 94 | ### Strings 95 | #### client.append(key, value) 96 | #### client.bitcount(key, [start], [end]) 97 | #### client.bitop(operation, destkey, key, [key ...]) 98 | #### client.bitpos(key, bit, [start], [end]) 99 | #### client.decr(key) 100 | #### client.decrby(key, decrement) 101 | #### client.get(key) 102 | #### client.getbit(key, offset) 103 | #### client.getrange(key, start, end) 104 | #### client.getset(key, value) 105 | #### client.incr(key) 106 | #### client.incrby(key, increment) 107 | #### client.incrbyfloat(key, increment) 108 | #### client.mget(key, [key ...]) | client.mget([key1, key2, ...]) 109 | #### client.mset(key, value, [key, value ...]) | client.mset(object) 110 | #### client.msetnx(key, value, [key, value ...]) | client.msetnx(object) 111 | #### client.psetex(key, milliseconds, value) 112 | #### client.set(key, value, [EX seconds], [PX milliseconds], [NX|XX]) 113 | #### client.setbit(key, offset, value) 114 | #### client.setex(key, seconds, value) 115 | #### client.setnx(key, value) 116 | #### client.setrange(key, offset, value) 117 | #### client.strlen(key) 118 | 119 | ### Hashes 120 | #### client.hdel(key, field, [field ...]) 121 | #### client.hexists(key, field) 122 | #### client.hget(key, field) 123 | #### client.hgetall(key) 124 | #### client.hincrby(key, field, increment) 125 | #### client.hincrbyfloat(key, field, increment) 126 | #### client.hkeys(key) 127 | #### client.hlen(key) 128 | #### client.hmget(key, field, [field ...]) 129 | #### client.hmset(key, field, value, [field, value ...]) | client.hmset(key, object) 130 | #### client.hset(key, field, value) 131 | #### client.hsetnx(key, field, value) 132 | #### client.hvals(key) 133 | #### client.hscan(key, cursor, [MATCH pattern], [COUNT count]) 134 | 135 | ### Lists 136 | #### client.blpop(key, [key ...], timeout) 137 | #### client.brpop(key, [key ...], timeout) 138 | #### client.brpoplpush(source, destination, timeout) 139 | #### client.lindex(key, index) 140 | #### client.linsert(key, BEFORE|AFTER, pivot, value) 141 | #### client.llen(key) 142 | #### client.lpop(key) 143 | #### client.lpush(key, value, [value ...]) 144 | #### client.lpushx(key, value) 145 | #### client.lrange(key, start, stop) 146 | #### client.lrem(key, count, value) 147 | #### client.lset(key, index, value) 148 | #### client.ltrim(key, start, stop) 149 | #### client.rpop(key) 150 | #### client.rpoplpush(source, destination) 151 | #### client.rpush(key, value, [value ...]) 152 | #### client.rpushx(key, value) 153 | 154 | ### Sets 155 | #### client.sadd(key, member, [member ...]) 156 | #### client.scard(key) 157 | #### client.sdiff(key, [key ...]) 158 | #### client.sdiffstore(destination, key, [key ...]) 159 | #### client.sinter(key, [key ...]) 160 | #### client.sinterstore(destination, key, [key ...]) 161 | #### client.sismember(key, member) 162 | #### client.smembers(key) 163 | #### client.smove(source, destination, member) 164 | #### client.spop(key) 165 | #### client.srandmember(key, [count]) 166 | #### client.srem(key, member, [member ...]) 167 | #### client.sunion(key, [key ...]) 168 | #### client.sunionstore(destination, key, [key ...]) 169 | #### client.sscan(key, cursor, [MATCH, pattern], [COUNT, count]) 170 | 171 | ### Sorted Sets 172 | #### client.zadd(key, score, member, [score, member ...]) 173 | #### client.zcard(key) 174 | #### client.zcount(key, min, max) 175 | #### client.zincrby(key, increment, member) 176 | #### client.zinterstore(destination, numkeys, key, [key ...], [WEIGHTS, weight, [weight ...]], [AGGREGATE, SUM|MIN|MAX]) 177 | #### client.zlexcount(key, min, max) 178 | #### client.zrange(key, start, stop, [WITHSCORES]) 179 | #### client.zrangebylex(key, min, max, [LIMIT, offset, count]) 180 | #### client.zrevrangebylex(key, min, max, [LIMIT, offset, count]) 181 | #### client.zrangebyscore(key, min, max, [WITHSCORES], [LIMIT, offset, count]) 182 | #### client.zrank(key, member) 183 | #### client.zrem(key, member, [member ...]) 184 | #### client.zremrangebylex(key, min, max) 185 | #### client.zremrangebyrank(key, start, stop) 186 | #### client.zremrangebyscore(key, min, max) 187 | #### client.zrevrange(key, start, stop, [WITHSCORES]) 188 | #### client.zrevrangebyscore(key, max, min, [WITHSCORES], [LIMIT, offset, count]) 189 | #### client.zrevrank(key, member) 190 | #### client.zscore(key, member) 191 | #### client.zunionstore(destination, numkeys, key, [key ...], [WEIGHTS, weight, [weight ...]], [AGGREGATE, SUM|MIN|MAX]) 192 | #### client.zscan(key, cursor, [MATCH, pattern] [COUNT, count]) 193 | 194 | ### HyperLog 195 | #### client.pfadd(key, element, [element ...]) 196 | #### client.pfcount(key, [key ...]) 197 | #### client.pfmerge(destkey, sourcekey, [sourcekey ...]) 198 | 199 | ### Pub/Sub 200 | #### client.psubscribe(pattern, [pattern ...]) 201 | #### client.publish(channel, message) 202 | #### client.pubsub(subcommand, [argument, [argument ...]) 203 | #### client.punsubscribe(pattern [pattern ...]) 204 | #### client.subscribe(channel, [channel ...]) 205 | #### client.unsubscribe([channel, [channel ...]]) 206 | 207 | ### Transactions 208 | #### client.discard() 209 | #### client.exec() 210 | #### client.multi() 211 | #### client.unwatch() 212 | #### client.watch(key, [key ...]) 213 | 214 | ### Scripting 215 | #### client.eval(script, numkeys, key, [key ...], arg, [arg ...]) 216 | #### client.evalsha(sha1, numkeys, key, [key ...], arg, [arg ...]) 217 | #### client.evalauto(script, numkeys, key, [key ...], arg, [arg ...]) [custom command] 218 | #### client.script('EXISTS', script, [script ...]) 219 | #### client.script('FLUSH') 220 | #### client.script('KILL') 221 | #### client.script('LOAD', script) 222 | 223 | ### Connection 224 | #### client.auth(password) 225 | #### client.echo(message) 226 | #### client.ping() 227 | #### client.quit() 228 | #### client.select(index) 229 | 230 | ### Server 231 | #### client.bgrewriteaof() 232 | #### client.bgsave() 233 | #### client.client('KILL', [ip:port], [ID, client-id], [TYPE, normal|slave|pubsub], [ADDR, ip:port], [SKIPME, yes/no]) 234 | #### client.client('LIST') 235 | #### client.client('GETNAME') 236 | #### client.client('PAUSE', timeout) 237 | #### client.client('SETNAME', connection-name) 238 | #### client.cluster('SLOTS') 239 | #### client.command() 240 | #### client.command('COUNT') 241 | #### client.command('GETKEYS') 242 | #### client.command('INFO', command-name, [command-name ...]) 243 | #### client.config('GET', parameter) 244 | #### client.config('REWRITE') 245 | #### client.config('SET', parameter, value) 246 | #### client.config('RESETSTAT') 247 | #### client.dbsize() 248 | #### client.debug('OBJECT', key) 249 | #### client.debug('SEGFAULT') 250 | #### client.flushall() 251 | #### client.flushdb() 252 | #### client.info([section]) 253 | #### client.lastsave() 254 | #### client.monitor() 255 | #### client.role() 256 | #### client.save() 257 | #### client.shutdown([NOSAVE], [SAVE]) 258 | #### client.slaveof(host, port) 259 | #### client.slowlog(subcommand, [argument]) 260 | #### client.sync() 261 | #### client.time() 262 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file starting from version **v1.1.0**. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ----- 7 | ## [2.1.7, 2.2.1] - 2018-09-27 8 | 9 | ### Changed 10 | 11 | - support redisUrl array. 12 | 13 | ## [2.2.0] - 2018-07-01 14 | 15 | ### Changed 16 | 17 | - support redis v5 commands. 18 | 19 | ## [2.1.6] - 2017-10-12 20 | 21 | ### Fixed 22 | 23 | - fix evalauto command. 24 | 25 | ## [2.1.4] - 2017-10-12 26 | 27 | ### Fixed 28 | 29 | - Update dependencies. 30 | - fix evalauto command. 31 | 32 | ## [2.1.1] - 2017-06-03 33 | 34 | ### Changed 35 | 36 | - Update dependencies. 37 | - Add Node.js v8 to test. 38 | - Update dependencies, changed node engines to >= v4.5.0. 39 | 40 | ## [2.1.0] - 2017-04-27 41 | 42 | ### Changed 43 | 44 | - Improve code with ES2015 style. 45 | - remove `each` function and `slice` function. 46 | 47 | ## [2.0.0] - 2017-03-30 48 | 49 | ### Changed 50 | 51 | - Update dependencies, changed node engines to >= v4. 52 | 53 | ## [1.7.3] - 2016-10-27 54 | 55 | ### Changed 56 | 57 | - Update dependencies. 58 | 59 | ### Fixed 60 | 61 | - Fixed for docker-redis-cluster, https://github.com/thunks/thunk-redis/issues/19. 62 | 63 | ## [1.7.2] - 2016-10-02 64 | 65 | ### Changed 66 | 67 | - Update dependencies. 68 | 69 | ## [1.7.1] - 2016-09-14 70 | 71 | ### Changed 72 | 73 | - Update dependencies. 74 | 75 | ## [1.7.0] - 2016-08-21 76 | 77 | ### Changed 78 | 79 | - Update engines to ">=0.12". 80 | 81 | ## [1.6.7] - 2016-08-15 82 | 83 | ### Changed 84 | 85 | - Update dependencies. 86 | - Improved socket send. 87 | 88 | ## [1.6.6] - 2016-07-22 89 | 90 | ### Changed 91 | 92 | - Update dependencies. 93 | - Added "TOUCH" command. 94 | 95 | ## [1.6.5] - 2016-06-08 96 | 97 | ### Changed 98 | 99 | - #18 support redis URL: redis://USER:PASS@redis.com:5678 100 | 101 | ## [1.6.4] - 2016-06-07 102 | 103 | ### Changed 104 | 105 | - Update dependencies. 106 | 107 | ## [1.6.3] - 2016-05-29 108 | 109 | ### Changed 110 | 111 | - Improved performance. 112 | 113 | ## [1.6.2] - 2016-05-24 114 | 115 | ### Fixed 116 | 117 | - Fixed for PubSub keyword 'message'. 118 | 119 | ## [1.6.1] - 2016-05-24 120 | 121 | ### Changed 122 | 123 | - Removed "debug" module. 124 | - Update dependencies. 125 | 126 | ## [1.6.0] - 2016-05-07 127 | 128 | ### Changed 129 | 130 | - Supported redis v3.2. 131 | 132 | ## [1.5.4] - 2016-03-16 133 | 134 | ### Changed 135 | 136 | - Removed socket.cork account to a bug in node.js. 137 | 138 | ## [1.5.3] - 2016-03-14 139 | 140 | ### Changed 141 | 142 | - Update dependencies. 143 | 144 | ## [1.5.2] - 2016-03-01 145 | 146 | ### Changed 147 | 148 | - Improve connection. 149 | 150 | ### Fixed 151 | 152 | - Fixed for old redis(v2.8.x). 153 | 154 | ## [1.5.1] - 2016-02-21 155 | 156 | ### Changed 157 | 158 | - Update dependencies. 159 | - Improved code. 160 | 161 | ## [1.5.0] - 2016-02-18 162 | 163 | ### Changed 164 | 165 | - Removed auto-discover cluster nodes during initialization. Because the nodes information 166 | from "cluster slots" command includes local-host information. But it will anto-connect 167 | node by "MOVED" and "ASK". 168 | - Changed files structure. 169 | 170 | ## [1.4.1] - 2016-01-20 171 | 172 | ### Changed 173 | 174 | - Update dependencies. 175 | 176 | ## [1.4.0] - 2015-12-29 177 | 178 | ### Changed 179 | 180 | - Changed default `options.maxAttempts` to `5`. 181 | - Added `options.onlyMaster`, it is useful for replication mode. 182 | - Removed `options.handleError`. 183 | - Supported IPv6. 184 | 185 | ## [1.3.0] - 2015-12-13 186 | 187 | ### Changed 188 | 189 | - Added `options.pingInterval`. 190 | - Added `client.clientConnect`. 191 | 192 | ## [1.2.4] - 2015-11-29 193 | 194 | ### Changed 195 | 196 | - Update dependencies. 197 | 198 | ## [1.2.3] - 2015-11-27 199 | 200 | ### Fixed 201 | 202 | - Fixed for slave node. 203 | 204 | ## [1.2.2] - 2015-11-19 205 | 206 | ### Changed 207 | 208 | - Update description. 209 | 210 | ## [1.2.1] - 2015-11-18 211 | 212 | ### Changed 213 | 214 | - Improved performance. 215 | 216 | ## [1.2.0] - 2015-11-12 217 | 218 | ### Changed 219 | 220 | - Update `thunks` to v4.0.0. 221 | 222 | ## [1.1.1] - 2015-10-07 223 | 224 | ### Changed 225 | 226 | - Added `clientReady` method. 227 | 228 | ### Fixed 229 | 230 | - Fixed command `evalauto`. 231 | - Fixed for `ASK`. 232 | 233 | ## [1.1.0] - 2015-10-07 234 | 235 | ### Changed 236 | 237 | - Added custom command `evalauto`. 238 | - Used `const` instead of `var`. 239 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2018 thunks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # thunk-redis 2 | 3 | The fastest thunk/promise-based redis client, support all redis features. 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![Build Status][travis-image]][travis-url] 7 | [![Downloads][downloads-image]][downloads-url] 8 | [![js-standard-style][js-standard-image]][js-standard-url] 9 | 10 | ## [thunks](https://github.com/thunks/thunks) 11 | 12 | ## Implementations: 13 | 14 | - [thunk-ratelimiter](https://github.com/thunks/thunk-ratelimiter) The fastest abstract rate limiter. 15 | - [timed-queue](https://github.com/teambition/timed-queue) Distributed timed job queue, backed by Redis. 16 | 17 | ## Demo([examples](https://github.com/zensh/thunk-redis/blob/master/examples)) 18 | 19 | **https://raw.githubusercontent.com/antirez/redis/4.0/redis.conf** 20 | 21 | **Sugest set config `cluster-require-full-coverage` to `no` in redis cluster!** 22 | 23 | **default thunk API:** 24 | 25 | ```js 26 | const redis = require('../index') 27 | const thunk = require('thunks')() 28 | const client = redis.createClient({ 29 | database: 1 30 | }) 31 | 32 | client.on('connect', function () { 33 | console.log('redis connected!') 34 | }) 35 | 36 | client.info('server')(function (error, res) { 37 | console.log('redis server info:', res) 38 | return this.dbsize() 39 | })(function (error, res) { 40 | console.log('current database size:', res) 41 | // current database size: 0 42 | return this.select(0) 43 | })(function (error, res) { 44 | console.log('select database 0:', res) 45 | // select database 0: OK 46 | return thunk.all([ 47 | this.multi(), 48 | this.set('key', 'redis'), 49 | this.get('key'), 50 | this.exec() 51 | ]) 52 | })(function (error, res) { 53 | console.log('transactions:', res) 54 | // transactions: [ 'OK', 'QUEUED', 'QUEUED', [ 'OK', 'redis' ] ] 55 | return this.quit() 56 | })(function (error, res) { 57 | console.log('redis client quit:', res) 58 | // redis client quit: OK 59 | }) 60 | ``` 61 | 62 | **use promise API:** 63 | ```js 64 | const redis = require('../index') 65 | const Promise = require('bluebird') 66 | const client = redis.createClient({ 67 | database: 1, 68 | usePromise: true 69 | }) 70 | 71 | client.on('connect', function () { 72 | console.log('redis connected!') 73 | }) 74 | 75 | client 76 | .info('server') 77 | .then(function (res) { 78 | console.log('redis server info:', res) 79 | return client.dbsize() 80 | }) 81 | .then(function (res) { 82 | console.log('current database size:', res) 83 | // current database size: 0 84 | return client.select(0) 85 | }) 86 | .then(function (res) { 87 | console.log('select database 0:', res) 88 | // select database 0: OK 89 | return Promise.all([ 90 | client.multi(), 91 | client.set('key', 'redis'), 92 | client.get('key'), 93 | client.exec() 94 | ]) 95 | }) 96 | .then(function (res) { 97 | console.log('transactions:', res) 98 | // transactions: [ 'OK', 'QUEUED', 'QUEUED', [ 'OK', 'redis' ] ] 99 | return client.quit() 100 | }) 101 | .then(function (res) { 102 | console.log('redis client quit:', res) 103 | // redis client quit: OK 104 | }) 105 | .catch(function (err) { 106 | console.error(err) 107 | }) 108 | ``` 109 | 110 | **support generator in thunk API:** 111 | ```js 112 | const redis = require('thunk-redis') 113 | const client = redis.createClient() 114 | 115 | client.select(1)(function* (error, res) { 116 | console.log(error, res) 117 | 118 | yield this.set('foo', 'bar') 119 | yield this.set('bar', 'baz') 120 | 121 | console.log('foo -> %s', yield this.get('foo')) 122 | console.log('bar -> %s', yield this.get('bar')) 123 | 124 | var user = { 125 | id: 'u001', 126 | name: 'jay', 127 | age: 24 128 | } 129 | // transaction, it is different from node_redis! 130 | yield [ 131 | this.multi(), 132 | this.set(user.id, JSON.stringify(user)), 133 | this.zadd('userAge', user.age, user.id), 134 | this.pfadd('ageLog', user.age), 135 | this.exec() 136 | ] 137 | 138 | return this.quit() 139 | })(function (error, res) { 140 | console.log(error, res) 141 | }) 142 | ``` 143 | 144 | ## Benchmark 145 | 146 | Details: https://github.com/thunks/thunk-redis/issues/12 147 | 148 | ## Installation 149 | 150 | **Node.js:** 151 | 152 | ```bash 153 | npm install thunk-redis 154 | ``` 155 | 156 | ## API ([More](https://github.com/zensh/thunk-redis/blob/master/API.md)) 157 | 158 | 1. redis.createClient([addressArray], [options]) 159 | 2. redis.createClient([port], [host], [options]) 160 | 3. redis.createClient([address], [options]) 161 | 4. redis.calcSlot(str) 162 | 5. redis.log([...]) 163 | 164 | ### redis.log 165 | 166 | Helper tool, print result or error stack. 167 | 168 | ```js 169 | const client = redis.createClient() 170 | client.info()(redis.log) 171 | ``` 172 | 173 | ### redis.createClient 174 | 175 | ```js 176 | const client1 = redis.createClient() 177 | const client2 = redis.createClient({database: 2}) 178 | const client3 = redis.createClient(6379, {database: 2}) 179 | const client4 = redis.createClient('127.0.0.1:6379', {database: 2}) 180 | const client5 = redis.createClient(6379, '127.0.0.1', {database: 2}) 181 | // connect to 2 nodes 182 | const client6 = redis.createClient([6379, 6380]) 183 | const client7 = redis.createClient(['127.0.0.1:6379', '127.0.0.1:6380']) // IPv4 184 | const client8 = redis.createClient(['[::1]:6379', '[::1]:6380']) // IPv6 185 | ``` 186 | 187 | **redis cluster:** 188 | 189 | ```js 190 | // assume cluster: '127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7002', ... 191 | const client1 = redis.createClient(7000, options) // will auto find cluster nodes! 192 | const client2 = redis.createClient([7000, 7001, 7002], options) 193 | 194 | const client3 = redis.createClient([ 195 | '127.0.0.1:7000', 196 | '127.0.0.1:7001', 197 | '127.0.0.1:7002' 198 | ], options) 199 | 200 | const client4 = redis.createClient([ 201 | {host: '127.0.0.1', port: 7000}, 202 | {host: '127.0.0.1', port: 7001}, 203 | {host: '127.0.0.1', port: 7002}, 204 | ], options) 205 | // All of above will work, it will find redis nodes by self. 206 | 207 | // Create a client in cluster servers without cluster mode: 208 | const clientX = redis.createClient(7000, {clusterMode: false}) 209 | ``` 210 | 211 | - `options.onlyMaster`: *Optional*, Type: `Boolean`, Default: `true`. 212 | 213 | In replication mode, thunk-redis will try to connect master node and close slave node. 214 | 215 | - `options.authPass`: *Optional*, Type: `String`, Default: `''`. 216 | 217 | - `options.database`: *Optional*, Type: `Number`, Default: `0`. 218 | 219 | - `options.returnBuffers`: *Optional*, Type: `Boolean`, Default: `false`. 220 | 221 | - `options.usePromise`: *Optional*, Type: `Boolean`, Default: `false`. 222 | 223 | Export promise commands API. 224 | 225 | **Use default Promise:** 226 | ```js 227 | var redis = require('thunk-redis') 228 | var client = redis.createClient({ 229 | usePromise: true 230 | }) 231 | ``` 232 | 233 | - `options.noDelay`: *Optional*, Type: `Boolean`, Default: `true`. 234 | 235 | Disables the Nagle algorithm. By default TCP connections use the Nagle algorithm, they buffer data before sending it off. Setting true for noDelay will immediately fire off data each time socket.write() is called. 236 | 237 | - `options.retryMaxDelay`: *Optional*, Type: `Number`, Default: `5 * 60 * 1000`. 238 | 239 | By default every time the client tries to connect and fails time before reconnection (delay) almost multiply by `1.2`. This delay normally grows infinitely, but setting `retryMaxDelay` limits delay to maximum value, provided in milliseconds. 240 | 241 | - `options.maxAttempts`: *Optional*, Type: `Number`, Default: `20`. 242 | 243 | By default client will try reconnecting until connected. Setting `maxAttempts` limits total amount of reconnects. 244 | 245 | - `options.pingInterval`: *Optional*, Type: `Number`, Default: `0`. 246 | 247 | How many ms before sending a ping packet. There is no ping packet by default(`0` to disable). If redis server enable `timeout` config, this option will be useful. 248 | 249 | - `options.IPMap`: *Optional*, Type: `Object`, Default: `{}`. 250 | 251 | This option use o resolve redis internal IP and external IP problem https://github.com/thunks/thunk-redis/issues/19. Define it like: `{internalIP: externalIP}`, for example: 252 | ```js 253 | const cli = redis.createClient([ 254 | '127.0.0.1:7000', 255 | '127.0.0.1:7001', 256 | '127.0.0.1:7002', 257 | '127.0.0.1:7003', 258 | '127.0.0.1:7004', 259 | '127.0.0.1:7005' 260 | ], { 261 | IPMap: { 262 | '172.17.0.2:7000': '127.0.0.1:7000', 263 | '172.17.0.2:7001': '127.0.0.1:7001', 264 | '172.17.0.2:7002': '127.0.0.1:7002', 265 | '172.17.0.2:7003': '127.0.0.1:7003', 266 | '172.17.0.2:7004': '127.0.0.1:7004', 267 | '172.17.0.2:7005': '127.0.0.1:7005' 268 | } 269 | }) 270 | ``` 271 | 272 | [npm-url]: https://npmjs.org/package/thunk-redis 273 | [npm-image]: http://img.shields.io/npm/v/thunk-redis.svg 274 | 275 | [travis-url]: https://travis-ci.org/thunks/thunk-redis 276 | [travis-image]: http://img.shields.io/travis/thunks/thunk-redis.svg 277 | 278 | [downloads-url]: https://npmjs.org/package/thunk-redis 279 | [downloads-image]: http://img.shields.io/npm/dm/thunk-redis.svg?style=flat-square 280 | 281 | [js-standard-url]: https://github.com/feross/standard 282 | [js-standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat 283 | -------------------------------------------------------------------------------- /benchmark/cluster.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const assert = require('assert') 4 | const thunk = require('thunks')() 5 | const redis = require('..') 6 | const IoRedis = require('ioredis') 7 | 8 | thunk(function * () { 9 | let timeT = 0 10 | let timeI = 0 11 | const testLen = 100000 12 | const titleT = 'redis(T):' 13 | const titleI = 'redis(I):' 14 | const clientT = redis.createClient(7000) 15 | const clientI = new IoRedis.Cluster([ 16 | { port: 7000, host: '127.0.0.1' }, 17 | { port: 7001, host: '127.0.0.1' }, 18 | { port: 7002, host: '127.0.0.1' } 19 | ]) 20 | 21 | const queue = [] 22 | while (queue.length < testLen) queue.push(queue.length) 23 | 24 | const smallStr = 'teambition' 25 | const longStr = (new Array(4097).join('-')) 26 | 27 | function printResult (title, timeT, timeI) { 28 | console.log(titleT, title, Math.floor(testLen * 1000 / timeT) + ' ops/sec', '100%') 29 | console.log(titleI, title, Math.floor(testLen * 1000 / timeI) + ' ops/sec', ((timeT / timeI) * 100).toFixed(1) + '%') 30 | console.log('') 31 | } 32 | 33 | console.log(titleT + 'thunk-redis\n', yield clientT.cluster('info')) 34 | // ioRedis cluster can't work (v1.0.6) 35 | console.log(titleI + 'ioRedis\n', yield function (done) { clientI.cluster('info', done) }) 36 | console.log('Bench start:\n') 37 | 38 | let resT, resI 39 | 40 | // SET 41 | yield thunk.delay(100) 42 | 43 | timeT = Date.now() 44 | resT = yield queue.map(function () { 45 | return clientT.set('zensh_thunks_00000001', smallStr) 46 | }) 47 | timeT = Date.now() - timeT 48 | 49 | yield thunk.delay(100) 50 | 51 | timeI = Date.now() 52 | resI = yield queue.map(function () { 53 | return function (done) { clientI.set('zensh_thunks_00000001', smallStr, done) } 54 | }) 55 | timeI = Date.now() - timeI 56 | printResult('SET small string', timeT, timeI) 57 | 58 | resT.map(function (val) { 59 | assert.strictEqual(val, 'OK') 60 | }) 61 | resI.map(function (val) { 62 | assert.strictEqual(val, 'OK') 63 | }) 64 | 65 | // GET 66 | yield thunk.delay(100) 67 | 68 | timeT = Date.now() 69 | resT = yield queue.map(function () { 70 | return clientT.get('zensh_thunks_00000001') 71 | }) 72 | timeT = Date.now() - timeT 73 | 74 | yield thunk.delay(100) 75 | 76 | timeI = Date.now() 77 | resI = yield queue.map(function () { 78 | return function (done) { clientI.get('zensh_thunks_00000001', done) } 79 | }) 80 | timeI = Date.now() - timeI 81 | printResult('GET small string', timeT, timeI) 82 | 83 | resT.map(function (val) { 84 | assert.strictEqual(val, smallStr) 85 | }) 86 | resI.map(function (val) { 87 | assert.strictEqual(val, smallStr) 88 | }) 89 | 90 | // SET 91 | yield thunk.delay(100) 92 | 93 | timeT = Date.now() 94 | resT = yield queue.map(function () { 95 | return clientT.set('zensh_thunks_00000002', longStr) 96 | }) 97 | timeT = Date.now() - timeT 98 | 99 | yield thunk.delay(100) 100 | 101 | timeI = Date.now() 102 | resI = yield queue.map(function () { 103 | return function (done) { clientI.set('zensh_thunks_00000002', longStr, done) } 104 | }) 105 | timeI = Date.now() - timeI 106 | printResult('SET long string', timeT, timeI) 107 | 108 | resT.map(function (val) { 109 | assert.strictEqual(val, 'OK') 110 | }) 111 | resI.map(function (val) { 112 | assert.strictEqual(val, 'OK') 113 | }) 114 | 115 | // GET 116 | yield thunk.delay(100) 117 | 118 | timeT = Date.now() 119 | resT = yield queue.map(function () { 120 | return clientT.get('zensh_thunks_00000002') 121 | }) 122 | timeT = Date.now() - timeT 123 | 124 | yield thunk.delay(100) 125 | 126 | timeI = Date.now() 127 | resI = yield queue.map(function () { 128 | return function (done) { clientI.get('zensh_thunks_00000002', done) } 129 | }) 130 | timeI = Date.now() - timeI 131 | printResult('GET long string', timeT, timeI) 132 | 133 | resT.map(function (val) { 134 | assert.strictEqual(val, longStr) 135 | }) 136 | resI.map(function (val) { 137 | assert.strictEqual(val, longStr) 138 | }) 139 | 140 | // INCR 141 | yield thunk.delay(100) 142 | 143 | timeT = Date.now() 144 | yield queue.map(function () { 145 | return clientT.incr('zensh_thunks_00000003') 146 | }) 147 | timeT = Date.now() - timeT 148 | 149 | yield thunk.delay(100) 150 | 151 | timeI = Date.now() 152 | yield queue.map(function () { 153 | return function (done) { clientI.incr('zensh_thunks_00000003', done) } 154 | }) 155 | timeI = Date.now() - timeI 156 | printResult('INCR', timeT, timeI) 157 | 158 | // LPUSH 159 | yield thunk.delay(100) 160 | 161 | timeT = Date.now() 162 | yield queue.map(function () { 163 | return clientT.lpush('zensh_thunks_00000004', smallStr) 164 | }) 165 | timeT = Date.now() - timeT 166 | 167 | yield thunk.delay(100) 168 | 169 | timeI = Date.now() 170 | yield queue.map(function () { 171 | return function (done) { clientI.lpush('zensh_thunks_00000004', smallStr, done) } 172 | }) 173 | timeI = Date.now() - timeI 174 | printResult('LPUSH', timeT, timeI) 175 | 176 | // LRANGE 177 | yield thunk.delay(100) 178 | 179 | timeT = Date.now() 180 | yield queue.map(function () { 181 | return clientT.lrange('zensh_thunks_00000004', '0', '100') 182 | }) 183 | timeT = Date.now() - timeT 184 | 185 | yield thunk.delay(100) 186 | 187 | timeI = Date.now() 188 | yield queue.map(function () { 189 | return function (done) { clientI.lrange('zensh_thunks_00000004', '0', '100', done) } 190 | }) 191 | timeI = Date.now() - timeI 192 | printResult('LRANGE 100', timeT, timeI) 193 | 194 | yield thunk.delay(100) 195 | process.exit() 196 | })() 197 | -------------------------------------------------------------------------------- /benchmark/fib.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const db = require('db') 4 | const coroutine = require('coroutine') 5 | const dbs = 'redis://127.0.0.1' 6 | const client = db.open(dbs) 7 | 8 | var titleF = 'redis@fibjs:' 9 | var time = 0 10 | var testLen = 1000 11 | var smallStr = 'teambition' 12 | var longStr = (new Array(4097).join('-')) 13 | 14 | console.log(titleF, client.command('flushdb').toString()) 15 | console.log('Start...\n\n') 16 | 17 | // PING 18 | time = Date.now() 19 | coroutine.parallel(genTasks(testLen, function () { 20 | client.command('ping').toString() 21 | })) 22 | printResult('PING', Date.now() - time) 23 | 24 | // SET 25 | time = Date.now() 26 | coroutine.parallel(genTasks(testLen, function () { 27 | client.command('set', 'zensh_thunks_00000001', smallStr).toString() 28 | })) 29 | printResult('SET', Date.now() - time) 30 | 31 | // GET 32 | time = Date.now() 33 | coroutine.parallel(genTasks(testLen, function () { 34 | client.command('get', 'zensh_thunks_00000001').toString() 35 | })) 36 | printResult('GET', Date.now() - time) 37 | 38 | // SET 39 | time = Date.now() 40 | coroutine.parallel(genTasks(testLen, function () { 41 | client.command('set', 'zensh_thunks_00000002', longStr).toString() 42 | })) 43 | printResult('SET', Date.now() - time) 44 | 45 | // GET 46 | time = Date.now() 47 | coroutine.parallel(genTasks(testLen, function () { 48 | client.command('get', 'zensh_thunks_00000002').toString() 49 | })) 50 | printResult('GET', Date.now() - time) 51 | 52 | // INCR 53 | time = Date.now() 54 | coroutine.parallel(genTasks(testLen, function () { 55 | client.command('incr', 'zensh_thunks_00000003').toString() 56 | })) 57 | printResult('INCR', Date.now() - time) 58 | 59 | // LPUSH 60 | time = Date.now() 61 | coroutine.parallel(genTasks(testLen, function () { 62 | client.command('lpush', 'zensh_thunks_00000004', smallStr).toString() 63 | })) 64 | printResult('LPUSH', Date.now() - time) 65 | 66 | // LRANGE 67 | time = Date.now() 68 | coroutine.parallel(genTasks(testLen, function () { 69 | client.command('lrange', 'zensh_thunks_00000004', '0', '100').toString() 70 | })) 71 | printResult('LRANGE 100', Date.now() - time) 72 | 73 | function genTasks (count, task) { 74 | var tasks = [] 75 | while (count--) tasks.push(task) 76 | return tasks 77 | } 78 | 79 | function printResult (title, time) { 80 | console.log(titleF, title, Math.floor(testLen * 1000 / time) + ' ops/sec') 81 | console.log('') 82 | } 83 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const thunk = require('thunks')() 4 | const redis = require('..') 5 | const nodeRedis = require('redis') 6 | const IoRedis = require('ioredis') 7 | // const co = require('co') 8 | 9 | // test in thunks(thunk base) 10 | thunk(bench)(console.log.bind(console)) 11 | 12 | // // test in co(promise base) 13 | // co(bench) 14 | // .then(console.log.bind(console)) 15 | // .catch(console.error.bind(console)) 16 | 17 | function * bench () { 18 | let timeN = 0 19 | let timeT = 0 20 | let timeI = 0 21 | const testLen = 100000 22 | const titleN = 'redis(N):' 23 | const titleT = 'redis(T):' 24 | const titleI = 'redis(I):' 25 | const clientN = nodeRedis.createClient(6380) 26 | const clientT = redis.createClient(6381) 27 | const clientI = new IoRedis(6382) 28 | 29 | const queue = [] 30 | while (queue.length < testLen) queue.push(queue.length) 31 | 32 | const smallStr = 'teambition' 33 | const longStr = (new Array(4097).join('-')) 34 | 35 | function printResult (title, timeN, timeT, timeI) { 36 | console.log(`\n${title}:`) 37 | console.log(titleN, `${timeN}ms`, Math.floor(testLen * 1000 / timeN) + 'ops/sec', '100%') 38 | console.log(titleT, `${timeT}ms`, Math.floor(testLen * 1000 / timeT) + 'ops/sec', ((timeN / timeT) * 100).toFixed(1) + '%') 39 | console.log(titleI, `${timeI}ms`, Math.floor(testLen * 1000 / timeI) + 'ops/sec', ((timeN / timeI) * 100).toFixed(1) + '%') 40 | } 41 | 42 | console.log(titleN + 'node_redis ', yield function (done) { clientN.flushdb(done) }) 43 | console.log(titleT + 'thunk-redis ', yield clientT.flushdb()) 44 | console.log(titleI + 'ioRedis ', yield clientI.flushdb()) 45 | console.log(`Bench start:(${testLen})\n`) 46 | 47 | // PING concurrency(full thread) 48 | yield thunk.delay(100) 49 | 50 | timeN = Date.now() 51 | yield queue.map(function () { 52 | return function (done) { clientN.ping(done) } 53 | }) 54 | timeN = Date.now() - timeN 55 | 56 | yield thunk.delay(100) 57 | 58 | timeT = Date.now() 59 | yield queue.map(function () { 60 | return clientT.ping() 61 | }) 62 | timeT = Date.now() - timeT 63 | 64 | yield thunk.delay(100) 65 | 66 | timeI = Date.now() 67 | yield queue.map(function () { 68 | return clientI.ping() 69 | }) 70 | timeI = Date.now() - timeI 71 | printResult('PING concurrency(full thread)', timeN, timeT, timeI) 72 | 73 | // PING concurrency(1000 thread) 74 | yield thunk.delay(100) 75 | 76 | timeN = Date.now() 77 | yield function * () { 78 | let count = queue.length 79 | yield queue.slice(0, 1000).map(function () { 80 | return next 81 | }) 82 | 83 | function next (callback) { 84 | if (count > 0) { 85 | count-- 86 | clientN.ping(function (err) { 87 | if (!err) next(callback) 88 | else callback(err) 89 | }) 90 | } else callback() 91 | } 92 | } 93 | timeN = Date.now() - timeN 94 | 95 | yield thunk.delay(100) 96 | 97 | timeT = Date.now() 98 | yield function * () { 99 | let count = queue.length 100 | yield queue.slice(0, 1000).map(function () { 101 | return next 102 | }) 103 | 104 | function next (callback) { 105 | if (count > 0) { 106 | count-- 107 | clientT.ping()(function (err) { 108 | if (!err) next(callback) 109 | else callback(err) 110 | }) 111 | } else callback() 112 | } 113 | } 114 | timeT = Date.now() - timeT 115 | 116 | yield thunk.delay(100) 117 | 118 | timeI = Date.now() 119 | yield function * () { 120 | let count = queue.length 121 | yield queue.slice(0, 1000).map(function () { 122 | return next 123 | }) 124 | 125 | function next (callback) { 126 | if (count > 0) { 127 | count-- 128 | clientI.ping() 129 | .then(function () { 130 | next(callback) 131 | }) 132 | .catch(callback) 133 | } else callback() 134 | } 135 | } 136 | timeI = Date.now() - timeI 137 | printResult('PING concurrency(1000 thread)', timeN, timeT, timeI) 138 | 139 | // PING sequential 1 by 1 140 | yield thunk.delay(100) 141 | 142 | timeN = Date.now() 143 | for (let i = queue.length; i > 0; i--) { 144 | yield function (done) { clientN.ping(done) } 145 | } 146 | timeN = Date.now() - timeN 147 | 148 | yield thunk.delay(100) 149 | 150 | timeT = Date.now() 151 | for (let i = queue.length; i > 0; i--) { 152 | yield clientT.ping() 153 | } 154 | timeT = Date.now() - timeT 155 | 156 | yield thunk.delay(100) 157 | 158 | timeI = Date.now() 159 | for (let i = queue.length; i > 0; i--) { 160 | yield clientI.ping() 161 | } 162 | timeI = Date.now() - timeI 163 | printResult('PING sequential 1 by 1', timeN, timeT, timeI) 164 | 165 | // SET small string 166 | yield thunk.delay(100) 167 | 168 | timeN = Date.now() 169 | yield queue.map(function () { 170 | return function (done) { clientN.set('zensh_thunks_00000001', smallStr, done) } 171 | }) 172 | timeN = Date.now() - timeN 173 | 174 | yield thunk.delay(100) 175 | 176 | timeT = Date.now() 177 | yield queue.map(function () { 178 | return clientT.set('zensh_thunks_00000001', smallStr) 179 | }) 180 | timeT = Date.now() - timeT 181 | 182 | yield thunk.delay(100) 183 | 184 | timeI = Date.now() 185 | yield queue.map(function () { 186 | return clientI.set('zensh_thunks_00000001', smallStr) 187 | }) 188 | timeI = Date.now() - timeI 189 | printResult('SET small string', timeN, timeT, timeI) 190 | 191 | // GET small string 192 | yield thunk.delay(100) 193 | 194 | timeN = Date.now() 195 | yield queue.map(function () { 196 | return function (done) { clientN.get('zensh_thunks_00000001', done) } 197 | }) 198 | timeN = Date.now() - timeN 199 | 200 | yield thunk.delay(100) 201 | 202 | timeT = Date.now() 203 | yield queue.map(function () { 204 | return clientT.get('zensh_thunks_00000001') 205 | }) 206 | timeT = Date.now() - timeT 207 | 208 | yield thunk.delay(100) 209 | 210 | timeI = Date.now() 211 | yield queue.map(function () { 212 | return clientI.get('zensh_thunks_00000001') 213 | }) 214 | timeI = Date.now() - timeI 215 | printResult('GET small string', timeN, timeT, timeI) 216 | 217 | // SET long string 218 | yield thunk.delay(100) 219 | 220 | timeN = Date.now() 221 | yield queue.map(function () { 222 | return function (done) { clientN.set('zensh_thunks_00000002', longStr, done) } 223 | }) 224 | timeN = Date.now() - timeN 225 | 226 | yield thunk.delay(100) 227 | 228 | timeT = Date.now() 229 | yield queue.map(function () { 230 | return clientT.set('zensh_thunks_00000002', longStr) 231 | }) 232 | timeT = Date.now() - timeT 233 | 234 | yield thunk.delay(100) 235 | 236 | timeI = Date.now() 237 | yield queue.map(function () { 238 | return clientI.set('zensh_thunks_00000002', longStr) 239 | }) 240 | timeI = Date.now() - timeI 241 | printResult('SET long string', timeN, timeT, timeI) 242 | 243 | // GET long string 244 | yield thunk.delay(100) 245 | 246 | timeN = Date.now() 247 | yield queue.map(function () { 248 | return function (done) { clientN.get('zensh_thunks_00000002', done) } 249 | }) 250 | timeN = Date.now() - timeN 251 | 252 | yield thunk.delay(100) 253 | 254 | timeT = Date.now() 255 | yield queue.map(function () { 256 | return clientT.get('zensh_thunks_00000002') 257 | }) 258 | timeT = Date.now() - timeT 259 | 260 | yield thunk.delay(100) 261 | 262 | timeI = Date.now() 263 | yield queue.map(function () { 264 | return clientI.get('zensh_thunks_00000002') 265 | }) 266 | timeI = Date.now() - timeI 267 | printResult('GET long string', timeN, timeT, timeI) 268 | 269 | // // INCR 270 | // yield thunk.delay(100) 271 | // 272 | // timeN = Date.now() 273 | // yield queue.map(function () { 274 | // return function (done) { clientN.incr('zensh_thunks_00000003', done) } 275 | // }) 276 | // timeN = Date.now() - timeN 277 | // 278 | // yield thunk.delay(100) 279 | // 280 | // timeT = Date.now() 281 | // yield queue.map(function () { 282 | // return clientT.incr('zensh_thunks_00000003') 283 | // }) 284 | // timeT = Date.now() - timeT 285 | // 286 | // yield thunk.delay(100) 287 | // 288 | // timeI = Date.now() 289 | // yield queue.map(function () { 290 | // return clientI.incr('zensh_thunks_00000003') 291 | // }) 292 | // timeI = Date.now() - timeI 293 | // printResult('INCR', timeN, timeT, timeI) 294 | // 295 | // // LPUSH 296 | // yield thunk.delay(100) 297 | // 298 | // timeN = Date.now() 299 | // yield queue.map(function () { 300 | // return function (done) { clientN.lpush('zensh_thunks_00000004', smallStr, done) } 301 | // }) 302 | // timeN = Date.now() - timeN 303 | // 304 | // yield thunk.delay(100) 305 | // 306 | // timeT = Date.now() 307 | // yield queue.map(function () { 308 | // return clientT.lpush('zensh_thunks_00000004', smallStr) 309 | // }) 310 | // timeT = Date.now() - timeT 311 | // 312 | // yield thunk.delay(100) 313 | // 314 | // timeI = Date.now() 315 | // yield queue.map(function () { 316 | // return clientI.lpush('zensh_thunks_00000004', smallStr) 317 | // }) 318 | // timeI = Date.now() - timeI 319 | // printResult('LPUSH', timeN, timeT, timeI) 320 | // 321 | // // LRANGE 322 | // yield thunk.delay(100) 323 | // 324 | // timeN = Date.now() 325 | // yield queue.map(function () { 326 | // return function (done) { clientN.lrange('zensh_thunks_00000004', '0', '100', done) } 327 | // }) 328 | // timeN = Date.now() - timeN 329 | // 330 | // yield thunk.delay(100) 331 | // 332 | // timeT = Date.now() 333 | // yield queue.map(function () { 334 | // return clientT.lrange('zensh_thunks_00000004', '0', '100') 335 | // }) 336 | // timeT = Date.now() - timeT 337 | // 338 | // yield thunk.delay(100) 339 | // 340 | // timeI = Date.now() 341 | // yield queue.map(function () { 342 | // return clientI.lrange('zensh_thunks_00000004', '0', '100') 343 | // }) 344 | // timeI = Date.now() - timeI 345 | // printResult('LRANGE 100', timeN, timeT, timeI) 346 | 347 | yield thunk.delay(100) 348 | process.exit() 349 | } 350 | -------------------------------------------------------------------------------- /check-commands: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const redis = require('./lib/index') 4 | const cli = redis.createClient(6379) 5 | 6 | cli.info()(function * (err, info) { 7 | if (err) throw err 8 | 9 | const add = [] 10 | const discard = [] 11 | const commandsInfo = {} 12 | let commands = yield cli.command() 13 | 14 | commands = commands.map(function (command) { 15 | commandsInfo[command[0]] = command.slice(1) 16 | return command[0] 17 | }) 18 | 19 | commands.reduce(function (add, command) { 20 | if (cli.clientCommands.indexOf(command) === -1) add.push(command) 21 | return add 22 | }, add) 23 | 24 | cli.clientCommands.reduce(function (discard, command) { 25 | if (commands.indexOf(command) === -1) discard.push(command) 26 | return discard 27 | }, discard) 28 | 29 | const all = {} 30 | Object.keys(commandsInfo).sort().forEach(function (command) { 31 | all[command] = commandsInfo[command] 32 | }) 33 | 34 | console.log(all) 35 | console.log('Add:', add) 36 | console.log('Discard:', discard) 37 | console.log('Version:', info.redis_version) 38 | process.exit() 39 | })() 40 | -------------------------------------------------------------------------------- /examples/cluster-transaction.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const redis = require('..') 4 | const client = redis.createClient(7000, { debugMode: false }) 5 | 6 | client.info()(function * () { 7 | let res = yield [ 8 | this.multi('key'), 9 | this.set('key', 'key'), 10 | this.get('key'), 11 | this.exec('key') 12 | ] 13 | console.log(res) // [ 'OK', 'QUEUED', 'QUEUED', [ 'OK', 'key' ] ] 14 | 15 | // Keys hash tags 16 | res = yield [ 17 | this.multi('hash{tag}'), 18 | this.set('hash{tag}', 'hash{tag}'), 19 | this.get('hash{tag}'), 20 | this.exec('hash{tag}') 21 | ] 22 | console.log(res) // [ 'OK', 'QUEUED', 'QUEUED', [ 'OK', 'hash{tag}' ] ] 23 | })() 24 | -------------------------------------------------------------------------------- /examples/demo-generator.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const redis = require('..') 4 | const client = redis.createClient() 5 | 6 | client.select(1)(function * (error, res) { 7 | console.log(error, res) 8 | 9 | yield this.set('foo', 'bar') 10 | yield this.set('bar', 'baz') 11 | 12 | console.log('foo -> %s', yield this.get('foo')) 13 | console.log('bar -> %s', yield this.get('bar')) 14 | 15 | const user = { 16 | id: 'u001', 17 | name: 'jay', 18 | age: 24 19 | } 20 | // transaction 21 | yield [ 22 | this.multi(), 23 | this.set(user.id, JSON.stringify(user)), 24 | this.zadd('userAge', user.age, user.id), 25 | this.pfadd('ageLog', user.age), 26 | this.exec() 27 | ] 28 | 29 | return this.quit() 30 | })(function (error, res) { 31 | console.log(error, res) 32 | }) 33 | -------------------------------------------------------------------------------- /examples/demo-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const redis = require('..') 4 | const Bluebird = require('bluebird') 5 | const client = redis.createClient({ 6 | database: 1, 7 | usePromise: Bluebird 8 | }) 9 | 10 | client.on('connect', function () { 11 | console.log('redis connected!') 12 | }) 13 | 14 | client 15 | .info('server') 16 | .then(function (res) { 17 | console.log('redis server info:', res) 18 | return client.dbsize() 19 | }) 20 | .then(function (res) { 21 | console.log('current database size:', res) 22 | // current database size: 0 23 | return client.select(0) 24 | }) 25 | .then(function (res) { 26 | console.log('select database 0:', res) 27 | // select database 0: OK 28 | return Promise.all([ 29 | client.multi(), 30 | client.set('key', 'redis'), 31 | client.get('key'), 32 | client.exec() 33 | ]) 34 | }) 35 | .then(function (res) { 36 | console.log('transactions:', res) 37 | // transactions: [ 'OK', 'QUEUED', 'QUEUED', [ 'OK', 'redis' ] ] 38 | return client.quit() 39 | }) 40 | .then(function (res) { 41 | console.log('redis client quit:', res) 42 | // redis client quit: OK 43 | }) 44 | .catch(function (err) { 45 | console.error(err) 46 | }) 47 | -------------------------------------------------------------------------------- /examples/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const redis = require('..') 4 | const thunk = require('thunks')() 5 | const client = redis.createClient({ 6 | database: 1 7 | }) 8 | 9 | client.on('connect', function () { 10 | console.log('redis connected!') 11 | }) 12 | 13 | client.info('server')(function (error, res) { 14 | console.log('redis server info:', res, error) 15 | return this.dbsize() 16 | })(function (error, res) { 17 | console.log('current database size:', res, error) 18 | // current database size: 0 19 | return this.select(0) 20 | })(function (error, res) { 21 | console.log('select database 0:', res, error) 22 | // select database 0: OK 23 | return thunk.all([ 24 | this.multi(), 25 | this.set('key', 'redis'), 26 | this.get('key'), 27 | this.exec() 28 | ]) 29 | })(function (error, res) { 30 | console.log('transactions:', res, error) 31 | // transactions: [ 'OK', 'QUEUED', 'QUEUED', [ 'OK', 'redis' ] ] 32 | return this.quit() 33 | })(function (error, res) { 34 | console.log('redis client quit:', res, error) 35 | // redis client quit: OK 36 | }) 37 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * thunk-redis - https://github.com/thunks/thunk-redis 4 | * 5 | * MIT Licensed 6 | */ 7 | 8 | const util = require('util') 9 | const thunks = require('thunks') 10 | const EventEmitter = require('events').EventEmitter 11 | 12 | const tool = require('./tool') 13 | const Queue = require('./queue') 14 | const initCommands = require('./commands').initCommands 15 | const createConnections = require('./connection').createConnections 16 | const thunk = thunks() 17 | 18 | let clientId = 0 19 | 20 | module.exports = RedisClient 21 | 22 | function RedisState (options, addressArray) { 23 | this.options = options 24 | this.addressArray = addressArray 25 | 26 | this.database = 0 27 | this.ended = false 28 | this.connected = false 29 | this.connection = null 30 | this.clusterMode = false 31 | this.timestamp = Date.now() 32 | this.clientId = ++clientId 33 | this.commandQueue = new Queue() 34 | this.pingInterval = null 35 | this.pool = Object.create(null) 36 | // { 37 | // '127.0.0.1:7001': connection 38 | // ... 39 | // } 40 | // masterSocket.replicationIds = ['127.0.0.1:7003', ...] 41 | 42 | this.slots = Object.create(null) 43 | // { 44 | // '0': masterConnectionId 45 | // '1': masterConnectionId 46 | // / ... 47 | // } 48 | this.IPMap = options.IPMap 49 | // { 50 | // '172.17.0.2:7000': '127.0.0.1:7000', 51 | // '172.17.0.2:7001': '127.0.0.1:7001', 52 | // '172.17.0.2:7002': '127.0.0.1:7002', 53 | // '172.17.0.2:7003': '127.0.0.1:7003', 54 | // '172.17.0.2:7004': '127.0.0.1:7004', 55 | // '172.17.0.2:7005': '127.0.0.1:7005' 56 | // } 57 | } 58 | 59 | RedisState.prototype.getConnection = function (slot) { 60 | let connection = slot != null && this.pool[this.slots[slot]] 61 | if (!connection || !connection.connected) connection = this.connection 62 | if (!connection || connection.ended) return new Error('connection(' + slot + ') not exist') 63 | return connection 64 | } 65 | 66 | RedisState.prototype.resetConnection = function () { 67 | if (this.connection && this.connection.connected) return 68 | 69 | let connection = null 70 | const keys = Object.keys(this.pool) 71 | for (let i = 0; i < keys.length; i++) { 72 | connection = this.pool[keys[i]] 73 | if (connection.connected) break 74 | } 75 | this.connection = connection 76 | } 77 | 78 | function RedisClient (addressArray, options) { 79 | EventEmitter.call(this) 80 | tool.setPrivate(this, '_redisState', new RedisState(options, addressArray)) 81 | 82 | const ctx = this 83 | this._redisState.thunkE = thunks(function (error) { 84 | ctx.emit('error', error) 85 | }) 86 | 87 | this.clientConnect() 88 | // useage: client.clientReady(taskFn), task will be called after connected 89 | this.clientReady = thunk.persist.call(this, function (callback) { 90 | ctx.once('connect', callback) 91 | }) 92 | } 93 | 94 | util.inherits(RedisClient, EventEmitter) 95 | initCommands(RedisClient.prototype) 96 | 97 | RedisClient.prototype.clientConnect = function () { 98 | const ctx = this 99 | const redisState = this._redisState 100 | redisState.ended = false 101 | createConnections(this, redisState.addressArray) 102 | 103 | // send a ping packet 104 | if (redisState.options.pingInterval && !redisState.pingInterval) { 105 | redisState.pingInterval = setInterval(function () { 106 | redisState.thunkE(ctx.ping())() 107 | }, redisState.options.pingInterval) 108 | } 109 | } 110 | 111 | // deprecate! 112 | RedisClient.prototype.clientSwitch = function (id) { 113 | console.warn('clientSwitch is deprecated, It will be removed in next version!') 114 | const redisState = this._redisState 115 | id = redisState.slots[id] || id 116 | if (!redisState.pool[id]) throw new Error(id + ' is not exist') 117 | redisState.slots[-1] = id 118 | return this 119 | } 120 | 121 | RedisClient.prototype.clientUnref = function () { 122 | if (this._redisState.ended) return 123 | for (const key of Object.keys(this._redisState.pool)) { 124 | const connection = this._redisState.pool[key] 125 | if (connection.connected) connection.socket.unref() 126 | else { 127 | connection.socket.once('connect', function () { 128 | this.unref() 129 | }) 130 | } 131 | } 132 | } 133 | 134 | RedisClient.prototype.clientEnd = function (hadError) { 135 | const redisState = this._redisState 136 | if (redisState.ended) return 137 | redisState.ended = true 138 | redisState.connected = false 139 | 140 | clearInterval(redisState.pingInterval) 141 | redisState.pingInterval = null 142 | 143 | for (const key of Object.keys(redisState.pool)) { 144 | const connection = redisState.pool[key] 145 | if (connection) connection.disconnect() 146 | } 147 | const commandQueue = redisState.commandQueue 148 | const message = (hadError && hadError.toString()) || 'The redis connection was ended' 149 | while (commandQueue.length) commandQueue.shift().callback(new Error(message)) 150 | 151 | this.emit('close', hadError) 152 | } 153 | 154 | RedisClient.prototype.clientState = function () { 155 | const redisState = this._redisState 156 | const state = { 157 | pool: {}, 158 | ended: redisState.ended, 159 | clientId: redisState.clientId, 160 | database: redisState.database, 161 | connected: redisState.connected, 162 | timestamp: redisState.timestamp, 163 | clusterMode: redisState.clusterMode, 164 | defaultConnection: redisState.slots[-1], 165 | commandQueueLength: redisState.commandQueue.length 166 | } 167 | 168 | for (const key of Object.keys(redisState.pool)) { 169 | const connection = redisState.pool[key] 170 | state.pool[connection.id] = connection.replicationIds ? connection.replicationIds.slice() : [] 171 | } 172 | return state 173 | } 174 | -------------------------------------------------------------------------------- /lib/commands.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * thunk-redis - https://github.com/thunks/thunk-redis 4 | * 5 | * MIT Licensed 6 | */ 7 | 8 | const thunk = require('thunks')() 9 | const tool = require('./tool') 10 | const calcSlot = require('./slot') 11 | const sendCommand = require('./connection').sendCommand 12 | 13 | // (redis_version:4.9.103) `redis-cli command` 14 | // `./check-commands` 15 | // http://redis.io/commands/command 16 | const commandsInfo = { 17 | append: [ 3, [ 'write', 'denyoom' ], 1, 1, 1 ], 18 | asking: [ 1, [ 'fast' ], 0, 0, 0 ], 19 | auth: [ 2, [ 'noscript', 'loading', 'stale', 'fast' ], 0, 0, 0 ], 20 | bgrewriteaof: [ 1, [ 'admin' ], 0, 0, 0 ], 21 | bgsave: [ -1, [ 'admin' ], 0, 0, 0 ], 22 | bitcount: [ -2, [ 'readonly' ], 1, 1, 1 ], 23 | bitfield: [ -2, [ 'write', 'denyoom' ], 1, 1, 1 ], 24 | bitop: [ -4, [ 'write', 'denyoom' ], 2, -1, 1 ], 25 | bitpos: [ -3, [ 'readonly' ], 1, 1, 1 ], 26 | blpop: [ -3, [ 'write', 'noscript' ], 1, -2, 1 ], 27 | brpop: [ -3, [ 'write', 'noscript' ], 1, -2, 1 ], 28 | brpoplpush: [ 4, [ 'write', 'denyoom', 'noscript' ], 1, 2, 1 ], 29 | bzpopmax: [ -2, [ 'write', 'noscript', 'fast' ], 1, -2, 1 ], 30 | bzpopmin: [ -2, [ 'write', 'noscript', 'fast' ], 1, -2, 1 ], 31 | client: [ -2, [ 'admin', 'noscript' ], 0, 0, 0 ], 32 | cluster: [ -2, [ 'admin' ], 0, 0, 0 ], 33 | command: [ 0, [ 'loading', 'stale' ], 0, 0, 0 ], 34 | config: [ -2, [ 'admin', 'loading', 'stale' ], 0, 0, 0 ], 35 | dbsize: [ 1, [ 'readonly', 'fast' ], 0, 0, 0 ], 36 | debug: [ -2, [ 'admin', 'noscript' ], 0, 0, 0 ], 37 | decr: [ 2, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 38 | decrby: [ 3, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 39 | del: [ -2, [ 'write' ], 1, -1, 1 ], 40 | discard: [ 1, [ 'noscript', 'fast' ], 0, 0, 0 ], 41 | dump: [ 2, [ 'readonly' ], 1, 1, 1 ], 42 | echo: [ 2, [ 'fast' ], 0, 0, 0 ], 43 | eval: [ -3, [ 'noscript', 'movablekeys' ], 0, 0, 0 ], 44 | evalsha: [ -3, [ 'noscript', 'movablekeys' ], 0, 0, 0 ], 45 | exec: [ 1, [ 'noscript', 'skip_monitor' ], 0, 0, 0 ], 46 | exists: [ -2, [ 'readonly', 'fast' ], 1, -1, 1 ], 47 | expire: [ 3, [ 'write', 'fast' ], 1, 1, 1 ], 48 | expireat: [ 3, [ 'write', 'fast' ], 1, 1, 1 ], 49 | flushall: [ -1, [ 'write' ], 0, 0, 0 ], 50 | flushdb: [ -1, [ 'write' ], 0, 0, 0 ], 51 | geoadd: [ -5, [ 'write', 'denyoom' ], 1, 1, 1 ], 52 | geodist: [ -4, [ 'readonly' ], 1, 1, 1 ], 53 | geohash: [ -2, [ 'readonly' ], 1, 1, 1 ], 54 | geopos: [ -2, [ 'readonly' ], 1, 1, 1 ], 55 | georadius: [ -6, [ 'write', 'movablekeys' ], 1, 1, 1 ], 56 | georadius_ro: [ -6, [ 'readonly', 'movablekeys' ], 1, 1, 1 ], 57 | georadiusbymember: [ -5, [ 'write', 'movablekeys' ], 1, 1, 1 ], 58 | georadiusbymember_ro: [ -5, [ 'readonly', 'movablekeys' ], 1, 1, 1 ], 59 | get: [ 2, [ 'readonly', 'fast' ], 1, 1, 1 ], 60 | getbit: [ 3, [ 'readonly', 'fast' ], 1, 1, 1 ], 61 | getrange: [ 4, [ 'readonly' ], 1, 1, 1 ], 62 | getset: [ 3, [ 'write', 'denyoom' ], 1, 1, 1 ], 63 | hdel: [ -3, [ 'write', 'fast' ], 1, 1, 1 ], 64 | hexists: [ 3, [ 'readonly', 'fast' ], 1, 1, 1 ], 65 | hget: [ 3, [ 'readonly', 'fast' ], 1, 1, 1 ], 66 | hgetall: [ 2, [ 'readonly' ], 1, 1, 1 ], 67 | hincrby: [ 4, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 68 | hincrbyfloat: [ 4, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 69 | hkeys: [ 2, [ 'readonly', 'sort_for_script' ], 1, 1, 1 ], 70 | hlen: [ 2, [ 'readonly', 'fast' ], 1, 1, 1 ], 71 | hmget: [ -3, [ 'readonly', 'fast' ], 1, 1, 1 ], 72 | hmset: [ -4, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 73 | 'host:': [ -1, [ 'loading', 'stale' ], 0, 0, 0 ], 74 | hscan: [ -3, [ 'readonly', 'random' ], 1, 1, 1 ], 75 | hset: [ -4, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 76 | hsetnx: [ 4, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 77 | hstrlen: [ 3, [ 'readonly', 'fast' ], 1, 1, 1 ], 78 | hvals: [ 2, [ 'readonly', 'sort_for_script' ], 1, 1, 1 ], 79 | incr: [ 2, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 80 | incrby: [ 3, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 81 | incrbyfloat: [ 3, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 82 | info: [ -1, [ 'loading', 'stale' ], 0, 0, 0 ], 83 | keys: [ 2, [ 'readonly', 'sort_for_script' ], 0, 0, 0 ], 84 | lastsave: [ 1, [ 'random', 'fast' ], 0, 0, 0 ], 85 | latency: [ -2, [ 'admin', 'noscript', 'loading', 'stale' ], 0, 0, 0 ], 86 | lindex: [ 3, [ 'readonly' ], 1, 1, 1 ], 87 | linsert: [ 5, [ 'write', 'denyoom' ], 1, 1, 1 ], 88 | llen: [ 2, [ 'readonly', 'fast' ], 1, 1, 1 ], 89 | lpop: [ 2, [ 'write', 'fast' ], 1, 1, 1 ], 90 | lpush: [ -3, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 91 | lpushx: [ -3, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 92 | lrange: [ 4, [ 'readonly' ], 1, 1, 1 ], 93 | lrem: [ 4, [ 'write' ], 1, 1, 1 ], 94 | lset: [ 4, [ 'write', 'denyoom' ], 1, 1, 1 ], 95 | ltrim: [ 4, [ 'write' ], 1, 1, 1 ], 96 | memory: [ -2, [ 'readonly' ], 0, 0, 0 ], 97 | mget: [ -2, [ 'readonly', 'fast' ], 1, -1, 1 ], 98 | migrate: [ -6, [ 'write', 'movablekeys' ], 0, 0, 0 ], 99 | module: [ -2, [ 'admin', 'noscript' ], 0, 0, 0 ], 100 | monitor: [ 1, [ 'admin', 'noscript' ], 0, 0, 0 ], 101 | move: [ 3, [ 'write', 'fast' ], 1, 1, 1 ], 102 | mset: [ -3, [ 'write', 'denyoom' ], 1, -1, 2 ], 103 | msetnx: [ -3, [ 'write', 'denyoom' ], 1, -1, 2 ], 104 | multi: [ 1, [ 'noscript', 'fast' ], 0, 0, 0 ], 105 | object: [ -2, [ 'readonly' ], 2, 2, 1 ], 106 | persist: [ 2, [ 'write', 'fast' ], 1, 1, 1 ], 107 | pexpire: [ 3, [ 'write', 'fast' ], 1, 1, 1 ], 108 | pexpireat: [ 3, [ 'write', 'fast' ], 1, 1, 1 ], 109 | pfadd: [ -2, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 110 | pfcount: [ -2, [ 'readonly' ], 1, -1, 1 ], 111 | pfdebug: [ -3, [ 'write' ], 0, 0, 0 ], 112 | pfmerge: [ -2, [ 'write', 'denyoom' ], 1, -1, 1 ], 113 | pfselftest: [ 1, [ 'admin' ], 0, 0, 0 ], 114 | ping: [ -1, [ 'stale', 'fast' ], 0, 0, 0 ], 115 | post: [ -1, [ 'loading', 'stale' ], 0, 0, 0 ], 116 | psetex: [ 4, [ 'write', 'denyoom' ], 1, 1, 1 ], 117 | psubscribe: [ -2, [ 'pubsub', 'noscript', 'loading', 'stale' ], 0, 0, 0 ], 118 | psync: [ 3, [ 'readonly', 'admin', 'noscript' ], 0, 0, 0 ], 119 | pttl: [ 2, [ 'readonly', 'fast' ], 1, 1, 1 ], 120 | publish: [ 3, [ 'pubsub', 'loading', 'stale', 'fast' ], 0, 0, 0 ], 121 | pubsub: [ -2, [ 'pubsub', 'random', 'loading', 'stale' ], 0, 0, 0 ], 122 | punsubscribe: [ -1, [ 'pubsub', 'noscript', 'loading', 'stale' ], 0, 0, 0 ], 123 | randomkey: [ 1, [ 'readonly', 'random' ], 0, 0, 0 ], 124 | readonly: [ 1, [ 'fast' ], 0, 0, 0 ], 125 | readwrite: [ 1, [ 'fast' ], 0, 0, 0 ], 126 | rename: [ 3, [ 'write' ], 1, 2, 1 ], 127 | renamenx: [ 3, [ 'write', 'fast' ], 1, 2, 1 ], 128 | replconf: [ -1, [ 'admin', 'noscript', 'loading', 'stale' ], 0, 0, 0 ], 129 | restore: [ -4, [ 'write', 'denyoom' ], 1, 1, 1 ], 130 | 'restore-asking': [ -4, [ 'write', 'denyoom', 'asking' ], 1, 1, 1 ], 131 | role: [ 1, [ 'noscript', 'loading', 'stale' ], 0, 0, 0 ], 132 | rpop: [ 2, [ 'write', 'fast' ], 1, 1, 1 ], 133 | rpoplpush: [ 3, [ 'write', 'denyoom' ], 1, 2, 1 ], 134 | rpush: [ -3, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 135 | rpushx: [ -3, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 136 | sadd: [ -3, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 137 | save: [ 1, [ 'admin', 'noscript' ], 0, 0, 0 ], 138 | scan: [ -2, [ 'readonly', 'random' ], 0, 0, 0 ], 139 | scard: [ 2, [ 'readonly', 'fast' ], 1, 1, 1 ], 140 | script: [ -2, [ 'noscript' ], 0, 0, 0 ], 141 | sdiff: [ -2, [ 'readonly', 'sort_for_script' ], 1, -1, 1 ], 142 | sdiffstore: [ -3, [ 'write', 'denyoom' ], 1, -1, 1 ], 143 | select: [ 2, [ 'loading', 'fast' ], 0, 0, 0 ], 144 | set: [ -3, [ 'write', 'denyoom' ], 1, 1, 1 ], 145 | setbit: [ 4, [ 'write', 'denyoom' ], 1, 1, 1 ], 146 | setex: [ 4, [ 'write', 'denyoom' ], 1, 1, 1 ], 147 | setnx: [ 3, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 148 | setrange: [ 4, [ 'write', 'denyoom' ], 1, 1, 1 ], 149 | shutdown: [ -1, [ 'admin', 'loading', 'stale' ], 0, 0, 0 ], 150 | sinter: [ -2, [ 'readonly', 'sort_for_script' ], 1, -1, 1 ], 151 | sinterstore: [ -3, [ 'write', 'denyoom' ], 1, -1, 1 ], 152 | sismember: [ 3, [ 'readonly', 'fast' ], 1, 1, 1 ], 153 | slaveof: [ 3, [ 'admin', 'noscript', 'stale' ], 0, 0, 0 ], 154 | slowlog: [ -2, [ 'admin' ], 0, 0, 0 ], 155 | smembers: [ 2, [ 'readonly', 'sort_for_script' ], 1, 1, 1 ], 156 | smove: [ 4, [ 'write', 'fast' ], 1, 2, 1 ], 157 | sort: [ -2, [ 'write', 'denyoom', 'movablekeys' ], 1, 1, 1 ], 158 | spop: [ -2, [ 'write', 'random', 'fast' ], 1, 1, 1 ], 159 | srandmember: [ -2, [ 'readonly', 'random' ], 1, 1, 1 ], 160 | srem: [ -3, [ 'write', 'fast' ], 1, 1, 1 ], 161 | sscan: [ -3, [ 'readonly', 'random' ], 1, 1, 1 ], 162 | strlen: [ 2, [ 'readonly', 'fast' ], 1, 1, 1 ], 163 | subscribe: [ -2, [ 'pubsub', 'noscript', 'loading', 'stale' ], 0, 0, 0 ], 164 | substr: [ 4, [ 'readonly' ], 1, 1, 1 ], 165 | sunion: [ -2, [ 'readonly', 'sort_for_script' ], 1, -1, 1 ], 166 | sunionstore: [ -3, [ 'write', 'denyoom' ], 1, -1, 1 ], 167 | swapdb: [ 3, [ 'write', 'fast' ], 0, 0, 0 ], 168 | sync: [ 1, [ 'readonly', 'admin', 'noscript' ], 0, 0, 0 ], 169 | time: [ 1, [ 'random', 'fast' ], 0, 0, 0 ], 170 | touch: [ -2, [ 'readonly', 'fast' ], 1, 1, 1 ], 171 | ttl: [ 2, [ 'readonly', 'fast' ], 1, 1, 1 ], 172 | type: [ 2, [ 'readonly', 'fast' ], 1, 1, 1 ], 173 | unlink: [ -2, [ 'write', 'fast' ], 1, -1, 1 ], 174 | unsubscribe: [ -1, [ 'pubsub', 'noscript', 'loading', 'stale' ], 0, 0, 0 ], 175 | unwatch: [ 1, [ 'noscript', 'fast' ], 0, 0, 0 ], 176 | wait: [ 3, [ 'noscript' ], 0, 0, 0 ], 177 | watch: [ -2, [ 'noscript', 'fast' ], 1, -1, 1 ], 178 | xack: [ -3, [ 'write', 'fast' ], 1, 1, 1 ], 179 | xadd: [ -5, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 180 | xclaim: [ -5, [ 'write', 'fast' ], 1, 1, 1 ], 181 | xdel: [ -2, [ 'write', 'fast' ], 1, 1, 1 ], 182 | xgroup: [ -2, [ 'write', 'denyoom' ], 2, 2, 1 ], 183 | xinfo: [ -2, [ 'readonly' ], 2, 2, 1 ], 184 | xlen: [ 2, [ 'readonly', 'fast' ], 1, 1, 1 ], 185 | xpending: [ -3, [ 'readonly' ], 1, 1, 1 ], 186 | xrange: [ -4, [ 'readonly' ], 1, 1, 1 ], 187 | xread: [ -3, [ 'readonly', 'noscript', 'movablekeys' ], 1, 1, 1 ], 188 | xreadgroup: [ -3, [ 'write', 'noscript', 'movablekeys' ], 1, 1, 1 ], 189 | xrevrange: [ -4, [ 'readonly' ], 1, 1, 1 ], 190 | xtrim: [ -2, [ 'write', 'fast' ], 1, 1, 1 ], 191 | zadd: [ -4, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 192 | zcard: [ 2, [ 'readonly', 'fast' ], 1, 1, 1 ], 193 | zcount: [ 4, [ 'readonly', 'fast' ], 1, 1, 1 ], 194 | zincrby: [ 4, [ 'write', 'denyoom', 'fast' ], 1, 1, 1 ], 195 | zinterstore: [ -4, [ 'write', 'denyoom', 'movablekeys' ], 0, 0, 0 ], 196 | zlexcount: [ 4, [ 'readonly', 'fast' ], 1, 1, 1 ], 197 | zpopmax: [ -2, [ 'write', 'fast' ], 1, -1, 1 ], 198 | zpopmin: [ -2, [ 'write', 'fast' ], 1, -1, 1 ], 199 | zrange: [ -4, [ 'readonly' ], 1, 1, 1 ], 200 | zrangebylex: [ -4, [ 'readonly' ], 1, 1, 1 ], 201 | zrangebyscore: [ -4, [ 'readonly' ], 1, 1, 1 ], 202 | zrank: [ 3, [ 'readonly', 'fast' ], 1, 1, 1 ], 203 | zrem: [ -3, [ 'write', 'fast' ], 1, 1, 1 ], 204 | zremrangebylex: [ 4, [ 'write' ], 1, 1, 1 ], 205 | zremrangebyrank: [ 4, [ 'write' ], 1, 1, 1 ], 206 | zremrangebyscore: [ 4, [ 'write' ], 1, 1, 1 ], 207 | zrevrange: [ -4, [ 'readonly' ], 1, 1, 1 ], 208 | zrevrangebylex: [ -4, [ 'readonly' ], 1, 1, 1 ], 209 | zrevrangebyscore: [ -4, [ 'readonly' ], 1, 1, 1 ], 210 | zrevrank: [ 3, [ 'readonly', 'fast' ], 1, 1, 1 ], 211 | zscan: [ -3, [ 'readonly', 'random' ], 1, 1, 1 ], 212 | zscore: [ 3, [ 'readonly', 'fast' ], 1, 1, 1 ], 213 | zunionstore: [ -4, [ 'write', 'denyoom', 'movablekeys' ], 0, 0, 0 ] 214 | } 215 | 216 | // fake QUIT command info~ 217 | commandsInfo.quit = [ 1, [ 'readonly', 'noscript' ], 0, 0, 0 ] 218 | 219 | // fake evalauto command info~ 220 | commandsInfo.evalauto = [ -3, [ 'noscript' ], 3, 3, 1 ] 221 | 222 | // no test, no documents 223 | // 'latency', 'pfdebug', 'pfselftest', 'psync', 'replconf', 'substr', 'wait', 'readonly', 224 | // 'readwrite', 'asking', 'cluster', 'restore-asking' 225 | 226 | const commands = Object.keys(commandsInfo) 227 | 228 | exports.initCommands = function (proto) { 229 | proto.clientCommands = commands 230 | 231 | proto.clientCalcSlot = function (reqArray) { 232 | const info = commandsInfo[reqArray[0]] 233 | if (!info || reqArray.length === 1 || (info[2] === 0 && info[0] !== 1)) return null 234 | // if command have no argument but user provide one, use the argument to calcSlot 235 | // it is useful in cluster for `multi`, `exec`, `discard`, `unwatch` and so on 236 | const keyIndex = info[2] || 1 237 | // Only calc first key, user should ensure that all keys are in a same node. 238 | const slot = calcSlot(reqArray[keyIndex]) 239 | if (info[0] === 1) reqArray.length = 1 240 | return slot 241 | } 242 | 243 | for (const command of commands) { 244 | proto[command] = function () { 245 | return sendCommand(this, command, adjustArgs(arguments)) 246 | } 247 | } 248 | 249 | /* overrides */ 250 | 251 | // Parse the reply from INFO into a hash. 252 | proto.info = function () { 253 | return sendCommand(this, 'info', adjustArgs(arguments), 0, formatInfo) 254 | } 255 | 256 | // Set the client's database property to the database number on SELECT. 257 | proto.select = function (database) { 258 | return sendCommand(this, 'select', [database], 0, function (reply) { 259 | this._redisState.database = +database 260 | return reply 261 | }) 262 | } 263 | 264 | // Optionally accept a hash as the only argument to MSET. 265 | proto.mset = function (hash) { 266 | return sendCommand(this, 'mset', isObject(hash) ? toArray(hash, []) : adjustArgs(arguments)) 267 | } 268 | 269 | // Optionally accept a hash as the only argument to MSETNX. 270 | proto.msetnx = function (hash) { 271 | return sendCommand(this, 'msetnx', isObject(hash) ? toArray(hash, []) : adjustArgs(arguments)) 272 | } 273 | 274 | // Optionally accept a hash as the first argument to HMSET after the key. 275 | proto.hmset = function (key, hash) { 276 | return sendCommand(this, 'hmset', isObject(hash) ? toArray(hash, [key]) : adjustArgs(arguments)) 277 | } 278 | 279 | // Make a hash from the result of HGETALL. 280 | proto.hgetall = function () { 281 | return sendCommand(this, 'hgetall', adjustArgs(arguments), 0, toHash) 282 | } 283 | 284 | proto.pubsub = function () { 285 | const args = adjustArgs(arguments) 286 | return sendCommand(this, 'pubsub', args, 0, function (res) { 287 | if (args[0].toLowerCase() === 'numsub') res = toHash(res) 288 | return res 289 | }) 290 | } 291 | 292 | proto.monitor = function (hashKey) { 293 | const args = adjustArgs(arguments) 294 | if (hashKey || !this._redisState.clusterMode) { 295 | return sendCommand(this, 'monitor', args) 296 | } 297 | 298 | // monit all nodes in cluster mode 299 | const tasks = [] 300 | for (const key of Object.keys(this._redisState.pool)) { 301 | const connection = this._redisState.pool[key] 302 | if (connection.monitorMode) return 303 | tasks.push(connection.sendCommand('monitor', args)) 304 | } 305 | 306 | return thunk.all.call(this, tasks) 307 | } 308 | 309 | /** 310 | * It combine `eval` and `evalsha` commands: accept a sript, load script to server 311 | * evaluate it through `evalsha`. 312 | * 313 | * Important feature: It will pre-position on redis cluster 314 | * @return thunk function 315 | * @api public 316 | */ 317 | 318 | proto.evalauto = function (script, numkeys, key) { 319 | const args = tool.slice(adjustArgs(arguments)) 320 | script = String(args[0]).trim() 321 | 322 | return thunk.call(this, function * () { 323 | yield this.clientReady 324 | 325 | const slot = args.length < 3 ? null : calcSlot(args[2]) 326 | let connection = this._redisState.getConnection(slot) 327 | if (connection instanceof Error) throw connection 328 | 329 | if (connection.evalshaP[script]) { 330 | args[0] = yield connection.evalshaP[script] 331 | try { 332 | return yield connection.sendCommand('evalsha', args) 333 | } catch (err) { 334 | if (err.name !== 'NOSCRIPT') throw err 335 | // try to load script 336 | args[0] = script 337 | } 338 | } 339 | 340 | const res = yield connection.sendCommand('eval', args) 341 | // connection maybe change with MOVED response 342 | connection = this._redisState.getConnection(slot) 343 | if (connection instanceof Error) throw connection 344 | connection.evalshaP[script] = thunk.promise(connection.sendCommand('script', ['load', script])) 345 | return res 346 | }) 347 | } 348 | 349 | for (const command of ['psubscribe', 'punsubscribe', 'subscribe', 'unsubscribe']) { 350 | proto[command] = function () { 351 | const args = adjustArgs(arguments) 352 | return sendCommand(this, command, args, args.length ? (args.length - 1) : 0) 353 | } 354 | } 355 | 356 | for (const command of commands) { 357 | proto[command.toUpperCase()] = proto[command] 358 | } 359 | } 360 | 361 | function isObject (obj) { 362 | return typeof obj === 'object' && !Array.isArray(obj) 363 | } 364 | 365 | function adjustArgs (args) { 366 | return Array.isArray(args[0]) ? args[0] : args 367 | } 368 | 369 | function toArray (hash, array) { 370 | for (const key of Object.keys(hash)) { 371 | array.push(key, hash[key]) 372 | } 373 | return array 374 | } 375 | 376 | function toHash (array) { 377 | const hash = {} 378 | 379 | for (let i = 0, len = array.length; i < len; i += 2) { 380 | hash[array[i]] = array[i + 1] 381 | } 382 | 383 | return hash 384 | } 385 | 386 | function formatInfo (info) { 387 | const hash = {} 388 | 389 | for (const line of info.toString().split('\r\n')) { 390 | const index = line.indexOf(':') 391 | 392 | if (index === -1) continue 393 | const name = line.slice(0, index) 394 | hash[name] = line.slice(index + 1) 395 | } 396 | 397 | return hash 398 | } 399 | -------------------------------------------------------------------------------- /lib/connection.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * thunk-redis - https://github.com/thunks/thunk-redis 4 | * 5 | * MIT Licensed 6 | */ 7 | 8 | const net = require('net') 9 | const Resp = require('respjs') 10 | 11 | const tool = require('./tool') 12 | const Queue = require('./queue') 13 | 14 | const thunk = require('thunks')() 15 | const HIGHT_WATER_MARK = 15 * 1024 16 | 17 | exports.sendCommand = sendCommand 18 | exports.wrapIPv6Address = wrapIPv6Address 19 | exports.createConnections = createConnections 20 | 21 | function sendCommand (redis, commandName, args, additionalCallbacks, responseHook) { 22 | return thunk.call(redis, function (callback) { 23 | const command = createCommand(this, commandName, args, callback, additionalCallbacks, responseHook) 24 | if (command) dispatchCommands(this, command) 25 | }) 26 | } 27 | 28 | function createConnections (redis, addressArray) { 29 | addressArray.forEach(function (id) { 30 | createConnection(redis, id) 31 | }) 32 | } 33 | 34 | function createConnection (redis, id) { 35 | const redisState = redis._redisState 36 | let connection = redisState.pool[id] 37 | if (!connection) connection = redisState.pool[id] = new Connection(redis, id) 38 | return connection 39 | } 40 | 41 | function Connection (redis, id) { 42 | this.id = id 43 | this.redis = redis 44 | 45 | this.attempts = 0 46 | this.retryDelay = 3000 47 | 48 | this.ended = false 49 | this.isMaster = false 50 | this.connected = false 51 | this.pubSubMode = false 52 | this.monitorMode = false 53 | this.queue = new Queue() 54 | this.pendingQueue = new Queue() 55 | this.pendingBytes = 0 56 | this.replicationIds = null 57 | this.bufBulk = redis._redisState.options.bufBulk 58 | this.connect() 59 | 60 | // cache script sha1 in persist thunk 61 | this.evalshaP = Object.create(null) 62 | } 63 | 64 | Connection.prototype.returnCommands = function () { 65 | this.rescuePending() 66 | this.queue.migrateTo(this.redis._redisState.commandQueue) 67 | return this 68 | } 69 | 70 | Connection.prototype.rescuePending = function () { 71 | while (this.pendingQueue.length) { 72 | const command = this.pendingQueue.pop() 73 | if (command.slot != null && command.name !== 'debug') this.queue.unshift(command) 74 | } 75 | return this 76 | } 77 | 78 | Connection.prototype.disconnect = function () { 79 | if (this.ended) return 80 | this.ended = true 81 | this.returnCommands() 82 | this.destroy() 83 | 84 | const redisState = this.redis._redisState 85 | delete redisState.pool[this.id] 86 | redisState.resetConnection() 87 | } 88 | 89 | Connection.prototype.destroy = function () { 90 | this.connected = false 91 | this.resp.removeAllListeners() 92 | this.socket.removeAllListeners() 93 | this.socket.end() 94 | this.socket.destroy() 95 | this.socket = null 96 | } 97 | 98 | Connection.prototype.connect = function () { 99 | const ctx = this 100 | 101 | this.connected = false 102 | if (this.socket) this.destroy() 103 | 104 | const address = unwrapAddress(this.id) 105 | const options = this.redis._redisState.options 106 | const socket = this.socket = net.createConnection({ 107 | host: address[0], 108 | port: +address[1] 109 | }) 110 | 111 | socket.setNoDelay(options.noDelay) 112 | socket.setTimeout(0) 113 | socket.setKeepAlive(true) 114 | 115 | this.resp = ctx.createResp() 116 | 117 | socket 118 | .once('connect', function () { 119 | // reset 120 | ctx.attempts = 0 121 | ctx.retryDelay = 3000 122 | 123 | ctx.checkConnection() 124 | }) 125 | .on('error', function (error) { 126 | ctx.redis.emit('error', error) 127 | }) 128 | .once('close', function (hadError) { 129 | ctx.reconnecting() 130 | }) 131 | .once('end', function () { 132 | if (!ctx.redis._redisState.clusterMode) ctx.tryRemove(null, true) 133 | }) 134 | 135 | socket.pipe(this.resp) 136 | return this 137 | } 138 | 139 | Connection.prototype.reconnecting = function () { 140 | const ctx = this 141 | const redisState = this.redis._redisState 142 | const options = redisState.options 143 | this.connected = false 144 | if (redisState.ended || this.ended) return 145 | 146 | redisState.resetConnection() 147 | this.attempts++ 148 | if (this.attempts <= options.maxAttempts) { 149 | this.rescuePending() 150 | this.retryDelay *= 1.2 151 | if (this.retryDelay >= options.retryMaxDelay) { 152 | this.retryDelay = options.retryMaxDelay 153 | } 154 | 155 | setTimeout(function () { 156 | ctx.connect() 157 | ctx.redis.emit('reconnecting', { 158 | delay: ctx.retryDelay, 159 | attempts: ctx.attempts 160 | }) 161 | }, this.retryDelay) 162 | } else { 163 | const err = new Error('Reconnect ECONNREFUSED ' + this.id) 164 | const address = unwrapAddress(this.id) 165 | err.errno = err.code = 'ECONNREFUSED' 166 | err.address = address[0] 167 | err.port = +address[1] 168 | err.attempts = this.attempts - 1 169 | this.tryRemove(err, true) 170 | } 171 | } 172 | 173 | Connection.prototype.checkConnection = function () { 174 | const ctx = this 175 | const redisState = this.redis._redisState 176 | const options = redisState.options 177 | 178 | redisState.thunkE(function (callback) { 179 | // auth 180 | if (!options.authPass) return callback() 181 | const command = createCommand(ctx.redis, 'auth', [options.authPass], callback) 182 | if (command) ctx.writeCommand(command) 183 | })(function () { 184 | // check replication and cluster 185 | return function (callback) { 186 | const command = createCommand(ctx.redis, 'info', ['default'], function (error, res) { 187 | if (!res) return callback(error) 188 | // 兼容 returnBuffer 模式 189 | res = res.toString() 190 | ctx.isMaster = checkMaster(res) 191 | redisState.clusterMode = checkCluster(res) 192 | // Replication 模式下如果只启用 master,则自动关闭 slave 193 | if (redisState.options.onlyMaster && !redisState.clusterMode && !ctx.isMaster) { 194 | return ctx.tryRemove(null, true) 195 | } 196 | callback() 197 | }) 198 | if (command) ctx.writeCommand(command) 199 | } 200 | })(function () { 201 | // check selected database 202 | if (redisState.clusterMode || !options.database) return 203 | return function (callback) { 204 | const command = createCommand(ctx.redis, 'select', [options.database], function (error, res) { 205 | if (error) return callback(error) 206 | redisState.database = options.database 207 | callback() 208 | }) 209 | if (command) ctx.writeCommand(command) 210 | } 211 | })(function () { 212 | ctx.connected = true 213 | redisState.resetConnection() 214 | ctx.redis.emit('connection', ctx) 215 | // default socket connected 216 | if (redisState.connected) ctx.flushCommand() 217 | else { 218 | redisState.connected = true 219 | ctx.redis.emit('connect') 220 | dispatchCommands(ctx.redis) 221 | } 222 | }) 223 | } 224 | 225 | Connection.prototype.createResp = function () { 226 | const ctx = this 227 | const redis = this.redis 228 | const redisState = redis._redisState 229 | const pendingQueue = this.pendingQueue 230 | 231 | return new Resp({ bufBulk: ctx.bufBulk }) 232 | .on('error', function (error) { 233 | ctx.rescuePending() 234 | redis.emit('error', error) 235 | }) 236 | .on('drain', function () { 237 | ctx.flushCommand() 238 | }) 239 | .on('data', function (data) { 240 | const command = pendingQueue.first() 241 | 242 | if (ctx.monitorMode && (!command || command.name !== 'quit')) { 243 | return redis.emit('monitor', data) 244 | } 245 | 246 | if (ctx.pubSubMode && isMessageReply(data)) { 247 | return redis.emit.apply(redis, data) 248 | } 249 | 250 | pendingQueue.shift() 251 | if (!command) { 252 | if (data[0] === 'unsubscribe' || data[0] === 'punsubscribe') { 253 | ctx.pubSubMode = data[2] > 0 254 | return redis.emit.apply(redis, data) 255 | } 256 | return redis.emit('error', new Error('Unexpected reply: ' + data)) 257 | } 258 | 259 | if (data instanceof Error) { 260 | data.node = ctx.id 261 | 262 | let id, ip, _connection 263 | switch (data.code) { 264 | case 'MOVED': 265 | ip = data.message.replace(/.+\s/, '') 266 | id = wrapIPv6Address(redisState.IPMap[ip] || ip) 267 | if (command.slot !== -1) redisState.slots[command.slot] = id 268 | _connection = createConnection(redis, id) 269 | _connection.flushCommand(command) 270 | break 271 | 272 | case 'ASK': 273 | ip = data.message.replace(/.+\s/, '') 274 | id = wrapIPv6Address(redisState.IPMap[ip] || ip) 275 | _connection = createConnection(redis, id) 276 | _connection.flushCommand(createCommand(redis, 'asking', [], function (error, res) { 277 | if (error) return command.callback(error) 278 | _connection.flushCommand(command) 279 | })) 280 | break 281 | 282 | case 'CLUSTERDOWN': 283 | command.callback(data) 284 | return redis.emit('error', data) 285 | 286 | default: 287 | command.callback(data) 288 | } 289 | 290 | return redis.emit('warn', data) 291 | } 292 | 293 | switch (command.name) { 294 | case 'monitor': 295 | ctx.monitorMode = true 296 | return command.callback(null, data) 297 | 298 | case 'subscribe': 299 | case 'psubscribe': 300 | ctx.pubSubMode = true 301 | command.callback() 302 | return redis.emit.apply(redis, data) 303 | 304 | case 'unsubscribe': 305 | case 'punsubscribe': 306 | ctx.pubSubMode = data[2] > 0 307 | command.callback() 308 | return redis.emit.apply(redis, data) 309 | 310 | default: 311 | return command.callback(null, data) 312 | } 313 | }) 314 | } 315 | 316 | Connection.prototype.tryRemove = function (error, tryEnd) { 317 | const redis = this.redis 318 | if (this.ended) return 319 | 320 | this.disconnect() 321 | if (error) redis.emit('error', error) 322 | if (tryEnd && !Object.keys(this.redis._redisState.pool).length) redis.clientEnd(error) 323 | else { 324 | // dispatch commands again 325 | process.nextTick(function () { 326 | dispatchCommands(redis) 327 | }) 328 | } 329 | } 330 | 331 | Connection.prototype.sendCommand = function (commandName, args, additionalCallbacks, responseHook) { 332 | const ctx = this 333 | return thunk.call(this.redis, function (callback) { 334 | const command = createCommand(this, commandName, args, callback, additionalCallbacks, responseHook) 335 | if (command) ctx.flushCommand(command) 336 | }) 337 | } 338 | 339 | Connection.prototype.flushCommand = function (command) { 340 | // `this.pendingBytes` lead to pipeline. 341 | if (!this.connected || this.pendingBytes) { 342 | if (command) this.queue.push(command) 343 | return this 344 | } 345 | 346 | const ctx = this 347 | let buf = null 348 | const bufs = [] 349 | while (this.queue.length && this.pendingBytes < HIGHT_WATER_MARK) { 350 | buf = this.compileCommand(this.queue.shift()) 351 | this.pendingBytes += buf.length 352 | bufs.push(buf) 353 | } 354 | if (command) { 355 | if (this.queue.length) this.queue.push(command) 356 | else { 357 | buf = this.compileCommand(command) 358 | this.pendingBytes += buf.length 359 | bufs.push(buf) 360 | } 361 | } 362 | if (this.pendingBytes) { 363 | buf = bufs.length === 1 ? bufs[0] : Buffer.concat(bufs, this.pendingBytes) 364 | this.socket.write(buf, function () { 365 | ctx.pendingBytes = 0 366 | ctx.flushCommand() 367 | }) 368 | } 369 | return this 370 | } 371 | 372 | Connection.prototype.compileCommand = function (command) { 373 | this.pendingQueue.push(command) 374 | let additionalCallbacks = command.additionalCallbacks 375 | while (additionalCallbacks-- > 0) { 376 | this.pendingQueue.push({ 377 | name: command.name, 378 | callback: noOp 379 | }) 380 | } 381 | return command.data 382 | } 383 | 384 | Connection.prototype.writeCommand = function (command) { 385 | this.socket.write(this.compileCommand(command)) 386 | } 387 | 388 | function Command (command, slot, data, callback, additionalCallbacks) { 389 | this.slot = slot 390 | this.data = data 391 | this.name = command 392 | this.callback = callback 393 | this.additionalCallbacks = additionalCallbacks || 0 394 | } 395 | 396 | function createCommand (redis, commandName, args, callback, additionalCallbacks, responseHook) { 397 | if (redis._redisState.ended) { 398 | callback(new Error('The redis client was ended')) 399 | return 400 | } 401 | 402 | const reqArray = tool.slice(args) 403 | reqArray.unshift(commandName) 404 | 405 | const _callback = !responseHook ? callback : function (err, res) { 406 | if (err != null) return callback(err) 407 | callback(null, responseHook.call(redis, res)) 408 | } 409 | 410 | let buffer 411 | let slot = null 412 | try { 413 | if (redis._redisState.clusterMode) slot = redis.clientCalcSlot(reqArray) 414 | buffer = Resp.encodeRequest(reqArray) 415 | return new Command(reqArray[0], slot, buffer, _callback, additionalCallbacks) 416 | } catch (error) { 417 | _callback(error) 418 | } 419 | } 420 | 421 | function dispatchCommands (redis, command) { 422 | const redisState = redis._redisState 423 | const commandQueue = redisState.commandQueue 424 | 425 | if (!redisState.connected) { 426 | if (command) commandQueue.push(command) 427 | return 428 | } 429 | 430 | while (commandQueue.length) dispatchCommand(redisState, commandQueue.shift()) 431 | if (command) dispatchCommand(redisState, command) 432 | } 433 | 434 | function dispatchCommand (redisState, command) { 435 | const res = redisState.getConnection(command.slot) 436 | if (res instanceof Connection) res.flushCommand(command) 437 | else { 438 | process.nextTick(function () { 439 | command.callback(res) 440 | }) 441 | } 442 | } 443 | 444 | const messageTypes = Object.create(null) 445 | messageTypes.message = true 446 | messageTypes.pmessage = true 447 | 448 | function isMessageReply (reply) { 449 | return reply && messageTypes[reply[0]] 450 | } 451 | 452 | function unwrapAddress (address) { 453 | return address.indexOf('[') === 0 ? address.slice(1).split(']:') : address.split(':') 454 | } 455 | 456 | // support IPv6 457 | // https://www.ietf.org/rfc/rfc2732.txt 458 | function wrapIPv6Address (host, port) { 459 | if (!port) { 460 | if (host.indexOf('[') === 0) return host 461 | host = host.split(':') 462 | port = host[1] 463 | host = host[0] 464 | } 465 | return '[' + host + ']:' + port 466 | } 467 | 468 | function checkMaster (info) { 469 | return info.indexOf('role:slave') === -1 470 | } 471 | 472 | function checkCluster (info) { 473 | return info.indexOf('cluster_enabled:1') > 0 474 | } 475 | 476 | function noOp () {} 477 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * thunk-redis - https://github.com/thunks/thunk-redis 4 | * 5 | * MIT Licensed 6 | */ 7 | 8 | const defaultPort = 6379 9 | const defaultHost = '127.0.0.1' 10 | const url = require('url') 11 | const tool = require('./tool') 12 | const calcSlot = require('./slot') 13 | const RedisClient = require('./client') 14 | const wrapIPv6Address = require('./connection').wrapIPv6Address 15 | 16 | exports.log = tool.log 17 | exports.calcSlot = calcSlot 18 | 19 | exports.createClient = function (port, host, options) { 20 | let addressArray 21 | 22 | if (Array.isArray(port)) { 23 | options = host || {} 24 | addressArray = normalizeNetAddress(port, options) 25 | } else if (port && typeof port.port === 'number') { 26 | options = host || {} 27 | addressArray = normalizeNetAddress([port], options) 28 | } else if (typeof port === 'string') { 29 | options = host || {} 30 | addressArray = normalizeNetAddress([port], options) 31 | } else if (typeof port === 'number') { 32 | if (typeof host !== 'string') { 33 | options = host || {} 34 | host = defaultHost 35 | } 36 | addressArray = normalizeNetAddress([{ 37 | port: port, 38 | host: host 39 | }], options) 40 | } else { 41 | options = port || {} 42 | addressArray = normalizeNetAddress([{ 43 | port: defaultPort, 44 | host: defaultHost 45 | }], options) 46 | } 47 | 48 | options.bufBulk = !!options.returnBuffers 49 | options.authPass = (options.authPass || '') + '' 50 | options.noDelay = options.noDelay == null ? true : !!options.noDelay 51 | options.onlyMaster = options.onlyMaster == null ? true : !!options.onlyMaster 52 | 53 | options.database = options.database > 0 ? Math.floor(options.database) : 0 54 | options.maxAttempts = options.maxAttempts >= 0 ? Math.floor(options.maxAttempts) : 5 55 | options.pingInterval = options.pingInterval >= 0 ? Math.floor(options.pingInterval) : 0 56 | options.retryMaxDelay = options.retryMaxDelay >= 3000 ? Math.floor(options.retryMaxDelay) : 5 * 60 * 1000 57 | 58 | // https://github.com/thunks/thunk-redis/issues/19 59 | // To resolve redis internal IP and external IP problem. For example: 60 | // options.IPMap = { 61 | // '172.17.0.2:7000': '127.0.0.1:7000', 62 | // '172.17.0.2:7001': '127.0.0.1:7001', 63 | // '172.17.0.2:7002': '127.0.0.1:7002', 64 | // '172.17.0.2:7003': '127.0.0.1:7003', 65 | // '172.17.0.2:7004': '127.0.0.1:7004', 66 | // '172.17.0.2:7005': '127.0.0.1:7005' 67 | // } 68 | options.IPMap = options.IPMap || {} 69 | 70 | const client = new RedisClient(addressArray, options) 71 | 72 | if (!options.usePromise) return client 73 | 74 | // if `options.usePromise` is available, export promise commands API for a client instance. 75 | for (const command of client.clientCommands) { 76 | const commandMethod = client[command] 77 | client[command] = client[command.toUpperCase()] = function () { 78 | const thunk = commandMethod.apply(this, arguments) 79 | return new Promise(function (resolve, reject) { 80 | thunk(function (err, res) { 81 | return err == null ? resolve(res) : reject(err) 82 | }) 83 | }) 84 | } 85 | } 86 | return client 87 | } 88 | 89 | // return ['[192.168.0.100]:6379', '[::192.9.5.5]:6379'] 90 | function normalizeNetAddress (array, options) { 91 | return array.map(function (address) { 92 | if (typeof address === 'string') { 93 | // Parse redis://USER:PASS@redis.io:5678 94 | if (address.indexOf('redis://') === 0) { 95 | const obj = url.parse(address) 96 | if (obj.auth) { 97 | options.authPass = obj.auth 98 | // https://www.iana.org/assignments/uri-schemes/prov/redis 99 | // redis://user:secret@localhost:6379 100 | const i = options.authPass.indexOf(':') 101 | if (i >= 0) { 102 | options.authPass = options.authPass.slice(i + 1) 103 | } 104 | } 105 | return wrapIPv6Address(obj.host) 106 | } 107 | return wrapIPv6Address(address) 108 | } 109 | if (typeof address === 'number') return wrapIPv6Address(defaultHost, address) 110 | address.host = address.host || defaultHost 111 | address.port = address.port || defaultPort 112 | return wrapIPv6Address(address.host, address.port) 113 | }) 114 | } 115 | exports.normalizeNetAddress = normalizeNetAddress 116 | -------------------------------------------------------------------------------- /lib/queue.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = class Queue { 4 | constructor () { 5 | this.tail = [] 6 | this.head = [] 7 | this.offset = 0 8 | this.hLength = 0 9 | } 10 | 11 | get length () { 12 | return this.hLength + this.tail.length - this.offset 13 | } 14 | 15 | first () { 16 | return this.hLength === this.offset ? this.tail[0] : this.head[this.offset] 17 | } 18 | 19 | push (item) { 20 | this.tail.push(item) 21 | } 22 | 23 | pop () { 24 | if (this.tail.length) return this.tail.pop() 25 | if (!this.hLength) return 26 | this.hLength-- 27 | return this.head.pop() 28 | } 29 | 30 | unshift (item) { 31 | if (!this.offset) { 32 | this.hLength++ 33 | this.head.unshift(item) 34 | } else { 35 | this.offset-- 36 | this.head[this.offset] = item 37 | } 38 | } 39 | 40 | shift () { 41 | if (this.offset === this.hLength) { 42 | if (!this.tail.length) return 43 | 44 | const tmp = this.head 45 | tmp.length = 0 46 | this.head = this.tail 47 | this.tail = tmp 48 | this.offset = 0 49 | this.hLength = this.head.length 50 | } 51 | return this.head[this.offset++] 52 | } 53 | 54 | migrateTo (queue) { 55 | let i = this.offset 56 | const len = this.tail.length 57 | while (i < this.hLength) queue.push(this.head[i++]) 58 | 59 | i = 0 60 | while (i < len) queue.push(this.tail[i++]) 61 | this.offset = this.hLength = this.head.length = this.tail.length = 0 62 | return queue 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/slot.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * thunk-redis - https://github.com/thunks/thunk-redis 4 | * 5 | * MIT Licensed 6 | */ 7 | 8 | module.exports = function (str) { 9 | str = str == null ? '' : str + '' 10 | // parse Keys hash tags 11 | const slot = str.match(/[^{]*{([^}]*).*/) 12 | return crc16(Buffer.from((slot && slot[1]) || str)) 13 | } 14 | 15 | /* CRC16 implementation according to CCITT standards. 16 | * 17 | * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" 18 | * Width : 16 bit 19 | * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) 20 | * Initialization : 0000 21 | * Reflect Input byte : False 22 | * Reflect Output CRC : False 23 | * Xor constant to output CRC : 0000 24 | * Output for "123456789" : 31C3 25 | */ 26 | const TABLE = new Int16Array([ 27 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 28 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 29 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 30 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 31 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 32 | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 33 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 34 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 35 | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 36 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 37 | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 38 | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 39 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 40 | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 41 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 42 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 43 | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 44 | 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 45 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 46 | 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 47 | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 48 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 49 | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 50 | 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 51 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 52 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 53 | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 54 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 55 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 56 | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 57 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 58 | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 59 | ]) 60 | 61 | function crc16 (buf) { 62 | let crc = 0 63 | for (let i = 0, len = buf.length; i < len; i++) { 64 | crc = (crc << 8) ^ TABLE[((crc >> 8) ^ buf[i]) & 0x00ff] 65 | } 66 | return crc & 16383 67 | } 68 | -------------------------------------------------------------------------------- /lib/tool.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * thunk-redis - https://github.com/thunks/thunk-redis 4 | * 5 | * MIT Licensed 6 | */ 7 | 8 | exports.setPrivate = function (ctx, key, value) { 9 | Object.defineProperty(ctx, key, { 10 | value: value, 11 | writable: false, 12 | enumerable: false, 13 | configurable: false 14 | }) 15 | } 16 | 17 | exports.slice = function (args, start) { 18 | start = start || 0 19 | if (start >= args.length) return [] 20 | let len = args.length 21 | const ret = Array(len - start) 22 | while (len-- > start) ret[len - start] = args[len] 23 | return ret 24 | } 25 | 26 | exports.log = function (err) { 27 | let silent = this.silent 28 | if (err instanceof Error) { 29 | arguments[0] = err.stack 30 | silent = false 31 | } 32 | if (!silent) console.log.apply(console, arguments) 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thunk-redis", 3 | "version": "2.2.4", 4 | "description": "The fastest thunk/promise-based redis client, support all redis features.", 5 | "main": "lib/index.js", 6 | "authors": [ 7 | "Yan Qing " 8 | ], 9 | "scripts": { 10 | "test": "standard && tman -t 10000 test/index.js test/commands", 11 | "test-full": "standard && tman -t 10000 test/index.js test/client2.js test/commands", 12 | "test-cluster": "tman -t 100000 test/cluster.js", 13 | "test-replica": "tman -t 10000 test/replica.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/thunks/thunk-redis.git" 18 | }, 19 | "keywords": [ 20 | "redis", 21 | "redis client", 22 | "cluster", 23 | "database", 24 | "thunk", 25 | "thunks", 26 | "generator", 27 | "promise", 28 | "pipelining" 29 | ], 30 | "engines": { 31 | "node": ">= 4.5.0" 32 | }, 33 | "dependencies": { 34 | "respjs": "^4.1.5", 35 | "thunks": "^4.9.3" 36 | }, 37 | "devDependencies": { 38 | "should": "^13.2.3", 39 | "standard": "^12.0.1", 40 | "tman": "^1.8.1" 41 | }, 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/thunks/thunk-redis/issues" 45 | }, 46 | "homepage": "https://github.com/thunks/thunk-redis", 47 | "files": [ 48 | "API.md", 49 | "README.md", 50 | "CHANGELOG.md", 51 | "index.js", 52 | "lib" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /test/client2.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const redis = require('..') 6 | 7 | tman.suite('createClient2', function () { 8 | const time = '' + Date.now() 9 | 10 | tman.it('redis.createClient()', function (done) { 11 | let connect = false 12 | const client = redis.createClient() 13 | 14 | client.on('connect', function () { 15 | connect = true 16 | }) 17 | client.info()(function (error, res) { 18 | should(error).be.equal(null) 19 | should(connect).be.equal(true) 20 | should(res.redis_version).be.type('string') 21 | return this.select(1) 22 | })(function (error, res) { 23 | should(error).be.equal(null) 24 | should(res).be.equal('OK') 25 | return this.set('test', time) 26 | })(function (error, res) { 27 | should(error).be.equal(null) 28 | should(res).be.equal('OK') 29 | this.clientEnd() 30 | })(done) 31 | }) 32 | 33 | tman.it('redis.createClient(address, options)', function (done) { 34 | let connect = false 35 | const client = redis.createClient('127.0.0.1:6379', { 36 | database: 1 37 | }) 38 | 39 | client.on('connect', function () { 40 | connect = true 41 | }) 42 | 43 | client.info()(function (error, res) { 44 | should(error).be.equal(null) 45 | should(connect).be.equal(true) 46 | should(res.redis_version).be.type('string') 47 | return this.clientEnd() 48 | })(done) 49 | }) 50 | 51 | tman.it('client.migrate', function (done) { 52 | const client = redis.createClient() 53 | const client2 = redis.createClient(6380) 54 | 55 | client.set('key', 123)(function (error, res) { 56 | should(error).be.equal(null) 57 | should(res).be.equal('OK') 58 | return client2.flushdb() 59 | })(function (error, res) { 60 | should(error).be.equal(null) 61 | should(res).be.equal('OK') 62 | return this.migrate('127.0.0.1', 6380, 'key', 0, 100) 63 | })(function (error, res) { 64 | should(error).be.equal(null) 65 | should(res).be.equal('OK') 66 | return this.exists('key') 67 | })(function (error, res) { 68 | should(error).be.equal(null) 69 | should(res).be.equal(0) 70 | return client2.get('key') 71 | })(function (error, res) { 72 | should(error).be.equal(null) 73 | should(res).be.equal('123') 74 | })(function () { 75 | this.clientEnd() 76 | client2.clientEnd() 77 | })(done) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/cluster.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const assert = require('assert') 5 | const thunk = require('thunks')() 6 | const redis = require('..') 7 | // Server: https://github.com/Grokzen/docker-redis-cluster 8 | const clusterHosts = [ 9 | '127.0.0.1:7000', 10 | '127.0.0.1:7001', 11 | '127.0.0.1:7002', 12 | '127.0.0.1:7003', 13 | '127.0.0.1:7004', 14 | '127.0.0.1:7005' 15 | ] 16 | const options = { 17 | IPMap: { 18 | '172.17.0.2:7000': '127.0.0.1:7000', 19 | '172.17.0.2:7001': '127.0.0.1:7001', 20 | '172.17.0.2:7002': '127.0.0.1:7002', 21 | '172.17.0.2:7003': '127.0.0.1:7003', 22 | '172.17.0.2:7004': '127.0.0.1:7004', 23 | '172.17.0.2:7005': '127.0.0.1:7005' 24 | } 25 | } 26 | 27 | const client = redis.createClient(clusterHosts, options) 28 | const count = 10000 29 | 30 | client.on('error', function (err) { 31 | console.log(JSON.stringify(err)) 32 | }) 33 | 34 | tman.before(function * () { 35 | console.log(yield client.cluster('slots')) 36 | }) 37 | 38 | tman.after(function * () { 39 | yield thunk.delay(1000) 40 | process.exit() 41 | }) 42 | 43 | tman.suite('cluster test', function () { 44 | tman.it('auto find node by "MOVED" and "ASK"', function * () { 45 | const clusterHosts2 = clusterHosts.slice() 46 | clusterHosts2.pop() // drop a node 47 | const client2 = redis.createClient(clusterHosts2, options) 48 | const task = [] 49 | let len = count 50 | while (len--) { 51 | task.push(thunk(len + '')(function * (_, res) { 52 | assert.strictEqual((yield client2.set(res, res)), 'OK') 53 | assert.strictEqual((yield client2.get(res)), res) 54 | if (!(res % 500)) process.stdout.write('.') 55 | })) 56 | } 57 | yield thunk.all(task) 58 | }) 59 | 60 | tman.it('create 10000 keys', function * () { 61 | const task = [] 62 | let len = count 63 | while (len--) { 64 | task.push(thunk(len + '')(function * (_, res) { 65 | assert.strictEqual((yield client.set(res, res)), 'OK') 66 | assert.strictEqual((yield client.get(res)), res) 67 | if (!(res % 500)) process.stdout.write('.') 68 | })) 69 | } 70 | yield thunk.all(task) 71 | }) 72 | 73 | tman.it('get 10000 keys', function * () { 74 | const task = [] 75 | let len = count 76 | while (len--) { 77 | task.push(thunk(len + '')(function * (_, res) { 78 | assert.strictEqual((yield client.get(res)), res) 79 | if (!(res % 500)) process.stdout.write('.') 80 | })) 81 | } 82 | yield thunk.all(task) 83 | }) 84 | 85 | tman.it.skip('transaction', function * () { 86 | for (let i = 0; i < count; i++) { 87 | const res = yield [ 88 | client.multi(), 89 | client.set(i, i), 90 | client.get(i), 91 | client.exec() 92 | ] 93 | console.log(111, res) 94 | assert.strictEqual(res[0], 'OK') 95 | assert.strictEqual(res[1], 'QUEUED') 96 | assert.strictEqual(res[2], 'QUEUED') 97 | assert.strictEqual(res[3][0], 'OK') 98 | assert.strictEqual(res[3][1], i + '') 99 | if (!(i % 500)) process.stdout.write('.') 100 | } 101 | }) 102 | 103 | tman.it('evalauto', function * () { 104 | const task = [] 105 | let len = count 106 | while (len--) addTask(len) 107 | yield thunk.all(task) 108 | 109 | function addTask (index) { 110 | task.push(function * () { 111 | const res = yield client.evalauto('return KEYS[1]', 1, index) 112 | assert.strictEqual(+res, index) 113 | if (!(index % 500)) process.stdout.write('.') 114 | return +res 115 | }) 116 | } 117 | }) 118 | 119 | tman.it.skip('kill a master', function * () { 120 | const task = [] 121 | const result = {} 122 | let len = 10000 123 | 124 | client.on('warn', function (err) { 125 | console.log(err) 126 | }) 127 | 128 | thunk.delay(100)(function () { 129 | // kill the default master node 130 | client.debug('segfault')() 131 | }) 132 | 133 | while (len--) { 134 | task.push(thunk(len + '')(function * (_, res) { 135 | return yield client.get(res) 136 | })(function (err, res) { 137 | assert.strictEqual(err, null) 138 | result[res] = true 139 | if (!(res % 500)) process.stdout.write('.') 140 | })) 141 | yield thunk.delay(5) 142 | } 143 | yield thunk.all(task) 144 | len = 10000 145 | while (len--) assert.strictEqual(result[len], true) 146 | }) 147 | }) 148 | -------------------------------------------------------------------------------- /test/commands/chaos.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const assert = require('assert') 5 | const redis = require('../..') 6 | 7 | tman.suite('chaos test', function () { 8 | const client = redis.createClient() 9 | const clientP = redis.createClient({ 10 | usePromise: true 11 | }) 12 | const len = 10000 13 | const tasks = [] 14 | for (let i = 0; i < len; i++) tasks.push(i) 15 | 16 | function getClient () { 17 | // use thunk API or promise API randomly 18 | return Math.floor(Math.random() * 10) % 2 ? client : clientP 19 | } 20 | 21 | tman.it('create 10000 users', function * () { 22 | assert((yield client.flushall()) === 'OK') 23 | yield tasks.map((value, index) => createUser('U' + index)) 24 | assert((yield client.zcard('userScore')) === len) 25 | 26 | function createUser (id) { 27 | const time = Date.now() 28 | const user = { 29 | id: id, 30 | name: 'user_' + id, 31 | email: id + '@test.com', 32 | score: 0, 33 | issues: [], 34 | createdAt: time, 35 | updatedAt: time 36 | } 37 | 38 | return new Promise((resolve, reject) => setTimeout(resolve, Math.floor(Math.random() * 5))) 39 | .then(() => { 40 | return Promise.all([ 41 | clientP.multi(), 42 | clientP.set(id, JSON.stringify(user)), 43 | clientP.zadd('userScore', user.score, id), 44 | clientP.exec() 45 | ]) 46 | }) 47 | .then((result) => assert.deepStrictEqual(result, ['OK', 'QUEUED', 'QUEUED', ['OK', 1]])) 48 | } 49 | }) 50 | 51 | tman.it('update 10000 users', function * () { 52 | yield tasks.map(function (value, index) { 53 | return updateUser('U' + index, Math.floor(Math.random() * 1000)) 54 | }) 55 | 56 | assert((yield client.pfcount('scoreLog')) > 5) 57 | 58 | function * updateUser (id, score) { 59 | const cli = getClient() 60 | let user = yield cli.get(id) 61 | user = JSON.parse(user) 62 | user.score = score 63 | user.updatedAt = Date.now() 64 | const result = yield [ 65 | cli.multi(), 66 | cli.set(id, JSON.stringify(user)), 67 | cli.zadd('userScore', user.score, id), 68 | cli.pfadd('scoreLog', Math.floor(score / 100)), 69 | cli.exec() 70 | ] 71 | result.pop() 72 | assert.deepStrictEqual(result, ['OK', 'QUEUED', 'QUEUED', 'QUEUED']) 73 | } 74 | }) 75 | 76 | tman.it('create 10000 issues for some users', function * () { 77 | yield tasks.map(function (value, index) { 78 | return createIssue('I' + index, 'U' + Math.floor(Math.random() * len)) 79 | }) 80 | 81 | assert((yield client.pfcount('scoreLog')) > 5) 82 | 83 | function * createIssue (id, uid) { 84 | const cli = getClient() 85 | const time = Date.now() 86 | const issue = { 87 | id: id, 88 | creatorId: uid, 89 | content: 'issue_' + id, 90 | createdAt: time, 91 | updatedAt: time 92 | } 93 | 94 | const user = JSON.parse(yield cli.get(uid)) 95 | if (!user) { 96 | console.log(uid, user) 97 | return 98 | } 99 | user.issues.push(issue.id) 100 | user.score += 100 101 | user.updatedAt = time 102 | 103 | const result = yield [ 104 | cli.multi(), 105 | cli.hmset(id, { 106 | issue: JSON.stringify(issue), 107 | creator: JSON.stringify({ 108 | id: user.id, 109 | name: user.name, 110 | email: user.email 111 | }) 112 | }), 113 | cli.set(uid, JSON.stringify(user)), 114 | cli.zadd('userScore', user.score, uid), 115 | cli.pfadd('scoreLog', Math.floor(user.score / 100)), 116 | cli.exec() 117 | ] 118 | result.pop() 119 | assert.deepStrictEqual(result, ['OK', 'QUEUED', 'QUEUED', 'QUEUED', 'QUEUED']) 120 | } 121 | }) 122 | }) 123 | -------------------------------------------------------------------------------- /test/commands/client.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const thunk = require('thunks').thunk 6 | const redis = require('../..') 7 | 8 | tman.suite('createClient', function () { 9 | const time = '' + Date.now() 10 | 11 | tman.it('redis.createClient()', function (done) { 12 | let connect = false 13 | const client = redis.createClient() 14 | 15 | client.on('connect', function () { 16 | connect = true 17 | }) 18 | client.info()(function (error, res) { 19 | should(error).be.equal(null) 20 | should(connect).be.equal(true) 21 | should(res.redis_version).be.type('string') 22 | return this.select(1) 23 | })(function (error, res) { 24 | should(error).be.equal(null) 25 | should(res).be.equal('OK') 26 | return this.set('test', time) 27 | })(function (error, res) { 28 | should(error).be.equal(null) 29 | should(res).be.equal('OK') 30 | this.clientEnd() 31 | })(done) 32 | }) 33 | 34 | tman.it('redis.createClient(options)', function (done) { 35 | const client = redis.createClient({ 36 | database: 1 37 | }) 38 | 39 | client.get('test')(function (error, res) { 40 | should(error).be.equal(null) 41 | should(res).be.equal(time) 42 | this.clientEnd() 43 | })(done) 44 | }) 45 | 46 | tman.it('redis.createClient({usePromise: true})', function (done) { 47 | const client = redis.createClient({ 48 | usePromise: true 49 | }) 50 | const promise = client.info() 51 | should(promise).be.instanceof(Promise) 52 | promise.then(function (res) { 53 | done() 54 | }, done) 55 | }) 56 | 57 | tman.it('redis.createClient({usePromise: Promise})', function (done) { 58 | const client = redis.createClient({ 59 | usePromise: Promise 60 | }) 61 | const promise = client.info() 62 | should(promise).be.instanceof(Promise) 63 | promise.then(function (res) { 64 | done() 65 | }, done) 66 | }) 67 | 68 | tman.it('redis.createClient(port, options)', function (done) { 69 | const client = redis.createClient(6379, { 70 | database: 1 71 | }) 72 | 73 | client.get('test')(function (error, res) { 74 | should(error).be.equal(null) 75 | should(res).be.equal(time) 76 | this.clientEnd() 77 | })(done) 78 | }) 79 | 80 | tman.it('redis.createClient(port, host, options)', function (done) { 81 | const client = redis.createClient(6379, 'localhost', { 82 | database: 1 83 | }) 84 | 85 | client.get('test')(function (error, res) { 86 | should(error).be.equal(null) 87 | should(res).be.equal(time) 88 | this.clientEnd() 89 | })(done) 90 | }) 91 | 92 | tman.it('redis.createClient(redisUrl, options) with error', function (done) { 93 | const client = redis.createClient('redis://USER:PASS@localhost:6379', { 94 | database: 1 95 | }) 96 | 97 | client.on('error', function (error) { 98 | should(error.message).be.containEql('AUTH') 99 | should(error.message).be.containEql('no password') 100 | done() 101 | }) 102 | }) 103 | 104 | tman.it('redis.createClient(redisUrl, options)', function (done) { 105 | const client = redis.createClient('redis://localhost:6379', { 106 | database: 1 107 | }) 108 | let client2 = null 109 | 110 | client.get('test')(function (error, res) { 111 | should(error).be.equal(null) 112 | should(res).be.equal(time) 113 | return this.config('set', 'requirepass', '123456') 114 | })(function (error, res) { 115 | should(error).be.equal(null) 116 | should(res).be.equal('OK') 117 | this.clientEnd() 118 | client2 = redis.createClient(['redis://123456@localhost:6379'], { 119 | database: 1 120 | }) 121 | return client2.get('test') 122 | })(function (error, res) { 123 | should(error).be.equal(null) 124 | should(res).be.equal(time) 125 | return client2.config('set', 'requirepass', '') 126 | })(function (error, res) { 127 | should(error).be.equal(null) 128 | should(res).be.equal('OK') 129 | })(done) 130 | }) 131 | }) 132 | 133 | tman.suite('client method', function () { 134 | tman.it('client.clientConnect, client.clientEnd and options.pingInterval', function (done) { 135 | const client = redis.createClient(6379, { 136 | pingInterval: 500 137 | }) 138 | 139 | const _ping = client.ping 140 | const pingCount = 0 141 | let _pingCount = 0 142 | client.ping = function () { 143 | return _ping.call(client)(function (err, res) { 144 | if (err) throw err 145 | _pingCount++ 146 | return res 147 | }) 148 | } 149 | 150 | thunk.delay(2200)(function () { 151 | const pingCount = _pingCount 152 | should(pingCount >= 4).be.equal(true) 153 | client.clientEnd() 154 | return thunk.delay(1000) 155 | })(function () { 156 | should(pingCount).be.equal(_pingCount) 157 | client.clientConnect() 158 | return thunk.delay(1200) 159 | })(function () { 160 | should(_pingCount >= (pingCount + 2)).be.equal(true) 161 | client.clientEnd() 162 | })(done) 163 | }) 164 | }) 165 | -------------------------------------------------------------------------------- /test/commands/connection.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const redis = require('../..') 6 | 7 | tman.suite('commands:Connection', function () { 8 | let client 9 | 10 | tman.before(function () { 11 | client = redis.createClient({ 12 | database: 0 13 | }) 14 | client.on('error', function (error) { 15 | console.error('redis client:', error) 16 | }) 17 | }) 18 | 19 | tman.beforeEach(function (done) { 20 | client.flushdb()(function (error, res) { 21 | should(error).be.equal(null) 22 | should(res).be.equal('OK') 23 | })(done) 24 | }) 25 | 26 | tman.after(function () { 27 | client.clientEnd() 28 | }) 29 | 30 | tman.it('client.echo', function (done) { 31 | client.echo('hello world!')(function (error, res) { 32 | should(error).be.equal(null) 33 | should(res).be.equal('hello world!') 34 | return this.echo(123) 35 | })(function (error, res) { 36 | should(error).be.equal(null) 37 | should(res).be.equal('123') 38 | })(done) 39 | }) 40 | 41 | tman.it('client.ping', function (done) { 42 | client.ping()(function (error, res) { 43 | should(error).be.equal(null) 44 | should(res).be.equal('PONG') 45 | })(done) 46 | }) 47 | 48 | tman.it('client.select', function (done) { 49 | client.select(10)(function (error, res) { 50 | should(error).be.equal(null) 51 | should(res).be.equal('OK') 52 | return this.select(99) 53 | })(function (error, res) { 54 | should(error).be.instanceOf(Error) 55 | should(res).be.equal(undefined) 56 | })(done) 57 | }) 58 | 59 | tman.it('client.auth', function (done) { 60 | client.auth('123456')(function (error, res) { 61 | should(error).be.instanceOf(Error) 62 | should(res).be.equal(undefined) 63 | return this.config('set', 'requirepass', '123456') 64 | })(function (error, res) { 65 | should(error).be.equal(null) 66 | should(res).be.equal('OK') 67 | return this.auth('123456') 68 | })(function (error, res) { 69 | should(error).be.equal(null) 70 | should(res).be.equal('OK') 71 | return this.config('set', 'requirepass', '') 72 | })(function (error, res) { 73 | should(error).be.equal(null) 74 | should(res).be.equal('OK') 75 | })(done) 76 | }) 77 | 78 | tman.it('client.quit', function (done) { 79 | client.quit()(function (error, res) { 80 | should(error).be.equal(null) 81 | should(res).be.equal('OK') 82 | })(done) 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /test/commands/geo.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const redis = require('../..') 6 | 7 | tman.suite('commands:Geo', function () { 8 | let client 9 | 10 | tman.before(function () { 11 | client = redis.createClient() 12 | client.on('error', function (error) { 13 | console.error('redis client:', error) 14 | }) 15 | }) 16 | 17 | tman.beforeEach(function (done) { 18 | client.flushdb()(function (error, res) { 19 | should(error).be.equal(null) 20 | should(res).be.equal('OK') 21 | })(done) 22 | }) 23 | 24 | tman.after(function () { 25 | client.clientEnd() 26 | }) 27 | 28 | tman.it('client.geoadd, client.geodist, client.georadius', function (done) { 29 | client.geoadd('Sicily', 13.361389, 38.115556, 'Palermo', 15.087269, 37.502669, 'Catania')(function (error, res) { 30 | if (error && /unknown command/.test(error.message)) { 31 | console.log('Do not support "geoadd"') 32 | return done() 33 | } 34 | should(error).be.equal(null) 35 | should(res).be.equal(2) 36 | return this.geodist('Sicily', 'Palermo', 'Catania')(function (error, res) { 37 | should(error).be.equal(null) 38 | should(Math.floor(res)).be.equal(166274) 39 | return this.geodist('Sicily', 'Palermo', 'Catania', 'km') 40 | })(function (error, res) { 41 | should(error).be.equal(null) 42 | should(Math.floor(res)).be.eql(166) 43 | return this.georadius('Sicily', 15, 37, 100, 'km') 44 | })(function (error, res) { 45 | should(error).be.equal(null) 46 | should(res).be.eql(['Catania']) 47 | return this.georadius('Sicily', 15, 37, 200, 'km') 48 | })(function (error, res) { 49 | should(error).be.equal(null) 50 | should(res).be.eql(['Palermo', 'Catania']) 51 | return this.georadius('Sicily', 15, 37, 200, 'km', 'WITHDIST') 52 | })(function (error, res) { 53 | should(error).be.equal(null) 54 | should(res[0][0]).be.equal('Palermo') 55 | should(Math.floor(res[0][1])).be.equal(190) 56 | should(res[1][0]).be.equal('Catania') 57 | should(Math.floor(res[1][1])).be.equal(56) 58 | return this.georadius('Sicily', 15, 37, 200, 'km', 'WITHCOORD') 59 | })(function (error, res) { 60 | should(error).be.equal(null) 61 | should(res[0][0]).be.equal('Palermo') 62 | should(Math.floor(res[0][1][0])).be.equal(13) 63 | should(Math.floor(res[0][1][1])).be.equal(38) 64 | should(res[1][0]).be.equal('Catania') 65 | should(Math.floor(res[1][1][0])).be.equal(15) 66 | should(Math.floor(res[1][1][1])).be.equal(37) 67 | })(done) 68 | }) 69 | }) 70 | 71 | tman.it('client.geohash', function (done) { 72 | client.geoadd('Sicily', 13.361389, 38.115556, 'Palermo', 15.087269, 37.502669, 'Catania')(function (error, res) { 73 | if (error && /unknown command/.test(error.message)) { 74 | console.log('Do not support "geoadd"') 75 | return done() 76 | } 77 | should(error).be.equal(null) 78 | should(res).be.equal(2) 79 | return this.zscore('Sicily', 'Palermo')(function (error, res) { 80 | should(error).be.equal(null) 81 | should(res).be.equal('3479099956230698') 82 | return this.geohash('Sicily', 'Palermo', 'Catania') 83 | })(function (error, res) { 84 | should(error).be.equal(null) 85 | should(res[0]).be.equal('sqc8b49rny0') 86 | should(res[1]).be.equal('sqdtr74hyu0') 87 | })(done) 88 | }) 89 | }) 90 | 91 | tman.it('client.geopos, client.georadiusbymember', function (done) { 92 | client.geoadd('Sicily', 13.361389, 38.115556, 'Palermo', 15.087269, 37.502669, 'Catania', 13.583333, 37.316667, 'Agrigento')(function (error, res) { 93 | if (error && /unknown command/.test(error.message)) { 94 | console.log('Do not support "geoadd"') 95 | return done() 96 | } 97 | should(error).be.equal(null) 98 | should(res).be.equal(3) 99 | return this.geopos('Sicily', 'Palermo', 'Catania', 'NonExisting')(function (error, res) { 100 | should(error).be.equal(null) 101 | should(Math.floor(res[0][0] * 1000)).be.equal(13361) 102 | should(Math.floor(res[0][1] * 1000)).be.equal(38115) 103 | should(Math.floor(res[1][0] * 1000)).be.equal(15087) 104 | should(Math.floor(res[1][1] * 1000)).be.equal(37502) 105 | should(res[2]).be.equal(null) 106 | return this.georadiusbymember('Sicily', 'Agrigento', 100, 'km') 107 | })(function (error, res) { 108 | should(error).be.equal(null) 109 | should(res).be.eql(['Agrigento', 'Palermo']) 110 | })(done) 111 | }) 112 | }) 113 | }) 114 | -------------------------------------------------------------------------------- /test/commands/hash.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const redis = require('../..') 6 | 7 | tman.suite('commands:Hash', function () { 8 | let client 9 | 10 | tman.before(function () { 11 | client = redis.createClient({ 12 | database: 0 13 | }) 14 | client.on('error', function (error) { 15 | console.error('redis client:', error) 16 | }) 17 | }) 18 | 19 | tman.beforeEach(function (done) { 20 | client.flushdb()(function (error, res) { 21 | should(error).be.equal(null) 22 | should(res).be.equal('OK') 23 | })(done) 24 | }) 25 | 26 | tman.after(function () { 27 | client.clientEnd() 28 | }) 29 | 30 | tman.it('client.hdel, client.hexists', function (done) { 31 | client.hdel('hash', 'key')(function (error, res) { 32 | should(error).be.equal(null) 33 | should(res).be.equal(0) 34 | return this.hexists('hash', 'key') 35 | })(function (error, res) { 36 | should(error).be.equal(null) 37 | should(res).be.equal(0) 38 | return this.hset('hash', 'key', 123) 39 | })(function (error, res) { 40 | should(error).be.equal(null) 41 | should(res).be.equal(1) 42 | return this.hexists('hash', 'key') 43 | })(function (error, res) { 44 | should(error).be.equal(null) 45 | should(res).be.equal(1) 46 | return this.hdel('hash', 'key') 47 | })(function (error, res) { 48 | should(error).be.equal(null) 49 | should(res).be.equal(1) 50 | return this.hmset('hash', { 51 | key1: 1, 52 | key2: 2 53 | }) 54 | })(function (error, res) { 55 | should(error).be.equal(null) 56 | should(res).be.equal('OK') 57 | return this.hdel('hash', 'key1', 'key2', 'key3') 58 | })(function (error, res) { 59 | should(error).be.equal(null) 60 | should(res).be.equal(2) 61 | })(done) 62 | }) 63 | 64 | tman.it('client.hget, client.hgetall, client.hkeys', function (done) { 65 | client.hget('hash', 'key')(function (error, res) { 66 | should(error).be.equal(null) 67 | should(res).be.equal(null) 68 | return this.hgetall('hash') 69 | })(function (error, res) { 70 | should(error).be.equal(null) 71 | should(res).be.eql({}) 72 | return this.hkeys('hash') 73 | })(function (error, res) { 74 | should(error).be.equal(null) 75 | should(res).be.eql([]) 76 | return this.hmset('hash', { 77 | key1: 1, 78 | key2: 2, 79 | key3: 3 80 | }) 81 | })(function (error, res) { 82 | should(error).be.equal(null) 83 | should(res).be.equal('OK') 84 | return this.hget('hash', 'key3') 85 | })(function (error, res) { 86 | should(error).be.equal(null) 87 | should(res).be.equal('3') 88 | return this.hgetall('hash') 89 | })(function (error, res) { 90 | should(error).be.equal(null) 91 | should(res).be.eql({ 92 | key1: '1', 93 | key2: '2', 94 | key3: '3' 95 | }) 96 | return this.hkeys('hash') 97 | })(function (error, res) { 98 | should(error).be.equal(null) 99 | should(res).be.eql(['key1', 'key2', 'key3']) 100 | })(done) 101 | }) 102 | 103 | tman.it('client.hincrby, client.hincrbyfloat', function (done) { 104 | client.hincrby('hash', 'key', -1)(function (error, res) { 105 | should(error).be.equal(null) 106 | should(res).be.equal(-1) 107 | return this.hincrby('hash', 'key', -9) 108 | })(function (error, res) { 109 | should(error).be.equal(null) 110 | should(res).be.equal(-10) 111 | return this.hincrby('hash', 'key', 15) 112 | })(function (error, res) { 113 | should(error).be.equal(null) 114 | should(res).be.equal(5) 115 | return this.hincrbyfloat('hash', 'key', -1.5) 116 | })(function (error, res) { 117 | should(error).be.equal(null) 118 | should(res).be.equal('3.5') 119 | return this.hset('hash', 'key1', 'hello') 120 | })(function (error, res) { 121 | should(error).be.equal(null) 122 | should(res).be.equal(1) 123 | return this.hincrbyfloat('hash', 'key1', 1)(function (error, res) { 124 | should(error).be.instanceOf(Error) 125 | }) 126 | })(done) 127 | }) 128 | 129 | tman.it('client.hlen, client.hmget, client.hmset', function (done) { 130 | client.hlen('hash')(function (error, res) { 131 | should(error).be.equal(null) 132 | should(res).be.equal(0) 133 | return this.hmget('hash', 'key1', 'key2') 134 | })(function (error, res) { 135 | should(error).be.equal(null) 136 | should(res).be.eql([null, null]) 137 | return this.hmset('hash', { 138 | key1: 1, 139 | key2: 2, 140 | key3: 3 141 | }) 142 | })(function (error, res) { 143 | should(error).be.equal(null) 144 | should(res).be.equal('OK') 145 | return this.hmget('hash', 'key3', 'key', 'key1') 146 | })(function (error, res) { 147 | should(error).be.equal(null) 148 | should(res).be.eql(['3', null, '1']) 149 | return this.hmset('hash', 'key', 0, 'key3', 'hello') 150 | })(function (error, res) { 151 | should(error).be.equal(null) 152 | should(res).be.equal('OK') 153 | return this.hlen('hash') 154 | })(function (error, res) { 155 | should(error).be.equal(null) 156 | should(res).be.equal(4) 157 | return this.hmget('hash', 'key3', 'key') 158 | })(function (error, res) { 159 | should(error).be.equal(null) 160 | should(res).be.eql(['hello', '0']) 161 | return this.set('key', 'hello') 162 | })(function (error, res) { 163 | should(error).be.equal(null) 164 | should(res).be.equal('OK') 165 | return this.hlen('key')(function (error, res) { 166 | should(error).be.instanceOf(Error) 167 | return this.hmset('key', 'key3', 'hello') 168 | })(function (error, res) { 169 | should(error).be.instanceOf(Error) 170 | }) 171 | })(done) 172 | }) 173 | 174 | tman.it('client.hset, client.hsetnx, client.hvals', function (done) { 175 | client.hvals('hash')(function (error, res) { 176 | should(error).be.equal(null) 177 | should(res).be.eql([]) 178 | return this.hset('hash', 'key', 123) 179 | })(function (error, res) { 180 | should(error).be.equal(null) 181 | should(res).be.equal(1) 182 | return this.hset('hash', 'key', 456) 183 | })(function (error, res) { 184 | should(error).be.equal(null) 185 | should(res).be.equal(0) 186 | return this.hget('hash', 'key') 187 | })(function (error, res) { 188 | should(error).be.equal(null) 189 | should(res).be.equal('456') 190 | return this.hsetnx('hash', 'key', 0) 191 | })(function (error, res) { 192 | should(error).be.equal(null) 193 | should(res).be.equal(0) 194 | return this.hget('hash', 'key') 195 | })(function (error, res) { 196 | should(error).be.equal(null) 197 | should(res).be.equal('456') 198 | return this.hsetnx('hash', 'key1', 'hello') 199 | })(function (error, res) { 200 | should(error).be.equal(null) 201 | should(res).be.equal(1) 202 | return this.hsetnx('hash1', 'key1', 'hello') 203 | })(function (error, res) { 204 | should(error).be.equal(null) 205 | should(res).be.equal(1) 206 | return this.hget('hash', 'key1') 207 | })(function (error, res) { 208 | should(error).be.equal(null) 209 | should(res).be.equal('hello') 210 | return this.hget('hash1', 'key1') 211 | })(function (error, res) { 212 | should(error).be.equal(null) 213 | should(res).be.equal('hello') 214 | return this.hvals('hash1') 215 | })(function (error, res) { 216 | should(error).be.equal(null) 217 | should(res).be.eql(['hello']) 218 | })(done) 219 | }) 220 | 221 | tman.it('client.hscan', function (done) { 222 | let count = 100 223 | const data = {} 224 | let scanKeys = [] 225 | 226 | while (count--) data['key' + count] = count 227 | 228 | function fullScan (key, cursor) { 229 | return client.hscan(key, cursor)(function (error, res) { 230 | should(error).be.equal(null) 231 | scanKeys = scanKeys.concat(res[1]) 232 | if (res[0] === '0') return res 233 | return fullScan(key, res[0]) 234 | }) 235 | } 236 | 237 | client.hscan('hash', 0)(function (error, res) { 238 | should(error).be.equal(null) 239 | should(res).be.eql(['0', []]) 240 | return client.hmset('hash', data) 241 | })(function (error, res) { 242 | should(error).be.equal(null) 243 | should(res).be.equal('OK') 244 | return fullScan('hash', 0) 245 | })(function (error, res) { 246 | should(error).be.equal(null) 247 | should(scanKeys.length).be.equal(200) 248 | for (const key of Object.keys(data)) { 249 | should(scanKeys).be.containEql(data[key] + '') 250 | should(scanKeys).be.containEql(key) 251 | } 252 | return this.hscan('hash', '0', 'count', 20) 253 | })(function (error, res) { 254 | should(error).be.equal(null) 255 | should(res[0] >= 0).be.equal(true) 256 | should(Object.keys(res[1]).length > 0).be.equal(true) 257 | return this.hscan('hash', '0', 'count', 200, 'match', '*0') 258 | })(function (error, res) { 259 | should(error).be.equal(null) 260 | should(res[0] === '0').be.equal(true) 261 | should(Object.keys(res[1]).length === 20).be.equal(true) 262 | })(done) 263 | }) 264 | 265 | tman.it('client.hstrlen', function (done) { 266 | client.hstrlen('key', 'f')(function (error, res) { 267 | if (error && /unknown command/.test(error.message)) { 268 | console.log('Do not support "hstrlen"') 269 | return done() 270 | } 271 | should(error).be.equal(null) 272 | should(res).be.equal(0) 273 | return this.hmset('key', 'f1', 'HelloWorld', 'f2', 99, 'f3', '-256')(function (error, res) { 274 | should(error).be.equal(null) 275 | should(res).be.equal('OK') 276 | return this.hstrlen('key', 'f0') 277 | })(function (error, res) { 278 | should(error).be.equal(null) 279 | should(res).be.equal(0) 280 | return this.hstrlen('key', 'f1') 281 | })(function (error, res) { 282 | should(error).be.equal(null) 283 | should(res).be.equal(10) 284 | return this.hstrlen('key', 'f2') 285 | })(function (error, res) { 286 | should(error).be.equal(null) 287 | should(res).be.equal(2) 288 | return this.hstrlen('key', 'f3') 289 | })(function (error, res) { 290 | should(error).be.equal(null) 291 | should(res).be.equal(4) 292 | })(done) 293 | }) 294 | }) 295 | }) 296 | -------------------------------------------------------------------------------- /test/commands/hyperloglog.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const thunk = require('thunks')() 6 | const redis = require('../..') 7 | 8 | tman.suite('commands:HyperLogLog', function () { 9 | let client 10 | 11 | tman.before(function () { 12 | client = redis.createClient({ 13 | database: 0 14 | }) 15 | client.on('error', function (error) { 16 | console.error('redis client:', error) 17 | }) 18 | }) 19 | 20 | tman.beforeEach(function (done) { 21 | client.flushdb()(function (error, res) { 22 | should(error).be.equal(null) 23 | should(res).be.equal('OK') 24 | })(done) 25 | }) 26 | 27 | tman.after(function () { 28 | client.clientEnd() 29 | }) 30 | 31 | tman.it('client.pfadd, client.pfcount, client.pfmerge', function (done) { 32 | client.pfadd('db', 'Redis', 'MongoDB', 'MySQL')(function (error, res) { 33 | should(error).be.equal(null) 34 | should(res).be.equal(1) 35 | return thunk.all(this.pfcount('db'), this.pfadd('db', 'Redis')) 36 | })(function (error, res) { 37 | should(error).be.equal(null) 38 | should(res).be.eql([3, 0]) 39 | return thunk.all(this.pfadd('db', 'PostgreSQL'), this.pfcount('db')) 40 | })(function (error, res) { 41 | should(error).be.equal(null) 42 | should(res).be.eql([1, 4]) 43 | return this.pfadd('alphabet', 'a', 'b', 'c') 44 | })(function (error, res) { 45 | should(error).be.equal(null) 46 | should(res).be.equal(1) 47 | return thunk.all(this.pfcount('alphabet'), this.pfcount('alphabet', 'db')) 48 | })(function (error, res) { 49 | should(error).be.equal(null) 50 | should(res).be.eql([3, 7]) 51 | return thunk.all(this.pfmerge('x', 'alphabet', 'db'), this.pfcount('x')) 52 | })(function (error, res) { 53 | should(error).be.equal(null) 54 | should(res).be.eql(['OK', 7]) 55 | })(done) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /test/commands/key.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const thunk = require('thunks')() 6 | const redis = require('../..') 7 | 8 | tman.suite('commands:Key', function () { 9 | let client 10 | 11 | tman.before(function () { 12 | client = redis.createClient({ 13 | database: 0 14 | }) 15 | client.on('error', function (error) { 16 | console.error('redis client:', error) 17 | }) 18 | }) 19 | 20 | tman.beforeEach(function (done) { 21 | client.flushdb()(function (error, res) { 22 | should(error).be.equal(null) 23 | should(res).be.equal('OK') 24 | })(done) 25 | }) 26 | 27 | tman.after(function () { 28 | client.clientEnd() 29 | }) 30 | 31 | tman.it('client.del, client.exists', function (done) { 32 | client.mset({ 33 | key: 1, 34 | key1: 2, 35 | key2: 3, 36 | key3: 4 37 | })(function (error, res) { 38 | should(error).be.equal(null) 39 | should(res).be.equal('OK') 40 | return this.exists('key') 41 | })(function (error, res) { 42 | should(error).be.equal(null) 43 | should(res).be.equal(1) 44 | return this.exists('key1') 45 | })(function (error, res) { 46 | should(error).be.equal(null) 47 | should(res).be.equal(1) 48 | return this.exists('key2') 49 | })(function (error, res) { 50 | should(error).be.equal(null) 51 | should(res).be.equal(1) 52 | return this.exists('key3') 53 | })(function (error, res) { 54 | should(error).be.equal(null) 55 | should(res).be.equal(1) 56 | return this.del('key') 57 | })(function (error, res) { 58 | should(error).be.equal(null) 59 | should(res).be.equal(1) 60 | return this.exists('key') 61 | })(function (error, res) { 62 | should(error).be.equal(null) 63 | should(res).be.equal(0) 64 | return this.del(['key1', 'key2', 'key3']) 65 | })(function (error, res) { 66 | should(error).be.equal(null) 67 | should(res).be.equal(3) 68 | return this.exists('key1') 69 | })(function (error, res) { 70 | should(error).be.equal(null) 71 | should(res).be.equal(0) 72 | return this.exists('key2') 73 | })(function (error, res) { 74 | should(error).be.equal(null) 75 | should(res).be.equal(0) 76 | return this.exists('key3') 77 | })(function (error, res) { 78 | should(error).be.equal(null) 79 | should(res).be.equal(0) 80 | return this.del('key') 81 | })(function (error, res) { 82 | should(error).be.equal(null) 83 | should(res).be.equal(0) 84 | })(done) 85 | }) 86 | 87 | tman.it('client.dump, client.restore', function (done) { 88 | let serializedValue 89 | const client2 = redis.createClient({ 90 | returnBuffers: true 91 | }) 92 | 93 | client2.dump('dumpKey')(function (error, res) { 94 | should(error).be.equal(null) 95 | should(res).be.equal(null) 96 | return this.set('dumpKey', 'hello, dump & restore!') 97 | })(function (error, res) { 98 | should(error).be.equal(null) 99 | should(res).be.equal('OK') 100 | return this.dump('dumpKey') 101 | })(function (error, res) { 102 | should(error).be.equal(null) 103 | should(Buffer.isBuffer(res)).be.equal(true) 104 | serializedValue = res 105 | return this.restore('restoreKey', 0, 'errorValue')(function (error, res) { 106 | should(error).be.instanceOf(Error) 107 | should(res).be.equal(undefined) 108 | return this.restore('restoreKey', 0, serializedValue) 109 | }) 110 | })(function (error, res) { 111 | should(error).be.equal(null) 112 | should(res).be.equal('OK') 113 | return this.get('restoreKey') 114 | })(function (error, res) { 115 | should(error).be.equal(null) 116 | should(Buffer.isBuffer(res)).be.equal(true) 117 | should(res.toString('utf8')).be.equal('hello, dump & restore!') 118 | return this.restore('restoreKey', 0, serializedValue)(function (error, res) { 119 | should(error).be.instanceOf(Error) 120 | should(res).be.equal(undefined) 121 | return this.get('restoreKey') 122 | }) 123 | })(function (error, res) { 124 | should(error).be.equal(null) 125 | should(Buffer.isBuffer(res)).be.equal(true) 126 | should(res.toString('utf8')).be.equal('hello, dump & restore!') 127 | return this.restore('key123', 1000, serializedValue) 128 | })(function (error, res) { 129 | should(error).be.equal(null) 130 | should(res).be.equal('OK') 131 | return this.get('key123') 132 | })(function (error, res) { 133 | should(error).be.equal(null) 134 | should(Buffer.isBuffer(res)).be.equal(true) 135 | should(res.toString('utf8')).be.equal('hello, dump & restore!') 136 | return thunk.delay(1100) 137 | })(function (error, res) { 138 | should(error).be.equal(null) 139 | return this.exists('key123') 140 | })(function (error, res) { 141 | should(error).be.equal(null) 142 | should(res).be.equal(0) 143 | })(done) 144 | }) 145 | 146 | tman.it('client.expire', function (done) { 147 | client.set('key', 123)(function (error, res) { 148 | should(error).be.equal(null) 149 | should(res).be.equal('OK') 150 | return this.exists('key') 151 | })(function (error, res) { 152 | should(error).be.equal(null) 153 | should(res).be.equal(1) 154 | return this.expire('key', 1) 155 | })(function (error, res) { 156 | should(error).be.equal(null) 157 | should(res).be.equal(1) 158 | return thunk.delay.call(this, 1010)(function () { 159 | return this.exists('key') 160 | }) 161 | })(function (error, res) { 162 | should(error).be.equal(null) 163 | should(res).be.equal(0) 164 | return this.expire('key', 1) 165 | })(function (error, res) { 166 | should(error).be.equal(null) 167 | should(res).be.equal(0) 168 | })(done) 169 | }) 170 | 171 | tman.it('client.expireat', function (done) { 172 | client.set('key', 123)(function (error, res) { 173 | should(error).be.equal(null) 174 | should(res).be.equal('OK') 175 | return this.exists('key') 176 | })(function (error, res) { 177 | should(error).be.equal(null) 178 | should(res).be.equal(1) 179 | return this.expireat('key', Math.floor(Date.now() / 1000 + 1)) 180 | })(function (error, res) { 181 | should(error).be.equal(null) 182 | should(res).be.equal(1) 183 | return thunk.delay.call(this, 1001)(function () { 184 | return this.exists('key') 185 | }) 186 | })(function (error, res) { 187 | should(error).be.equal(null) 188 | should(res).be.equal(0) 189 | return this.expireat('key', Math.floor(Date.now() / 1000 + 1)) 190 | })(function (error, res) { 191 | should(error).be.equal(null) 192 | should(res).be.equal(0) 193 | })(done) 194 | }) 195 | 196 | tman.it('client.keys', function (done) { 197 | client.keys('*')(function (error, res) { 198 | should(error).be.equal(null) 199 | should(res).be.eql([]) 200 | return this.mset({ 201 | a: 123, 202 | a1: 123, 203 | b: 123, 204 | b1: 123, 205 | c: 123, 206 | c1: 123 207 | }) 208 | })(function (error, res) { 209 | should(error).be.equal(null) 210 | should(res).be.equal('OK') 211 | return this.keys('*') 212 | })(function (error, res) { 213 | should(error).be.equal(null) 214 | should(res.sort()).be.eql(['a', 'a1', 'b', 'b1', 'c', 'c1']) 215 | return this.keys('a*') 216 | })(function (error, res) { 217 | should(error).be.equal(null) 218 | should(res.sort()).be.eql(['a', 'a1']) 219 | return this.keys('?1') 220 | })(function (error, res) { 221 | should(error).be.equal(null) 222 | should(res.sort()).be.eql(['a1', 'b1', 'c1']) 223 | })(done) 224 | }) 225 | 226 | tman.it('client.move', function (done) { 227 | client.mset({ 228 | key1: 1, 229 | key2: 2 230 | })(function (error, res) { 231 | should(error).be.equal(null) 232 | should(res).be.equal('OK') 233 | return this.select(1) 234 | })(function (error, res) { 235 | should(error).be.equal(null) 236 | should(res).be.equal('OK') 237 | return this.mset({ 238 | key2: 4, 239 | key3: 6 240 | }) 241 | })(function (error, res) { 242 | should(error).be.equal(null) 243 | should(res).be.equal('OK') 244 | return this.move('key2', 0) 245 | })(function (error, res) { 246 | should(error).be.equal(null) 247 | should(res).be.equal(0) 248 | return this.get('key2') 249 | })(function (error, res) { 250 | should(error).be.equal(null) 251 | should(res).be.equal('4') 252 | return this.move('key3', 0) 253 | })(function (error, res) { 254 | should(error).be.equal(null) 255 | should(res).be.equal(1) 256 | return this.exists('key3') 257 | })(function (error, res) { 258 | should(error).be.equal(null) 259 | should(res).be.equal(0) 260 | return this.move('key4', 0) 261 | })(function (error, res) { 262 | should(error).be.equal(null) 263 | should(res).be.equal(0) 264 | return this.select(0) 265 | })(function (error, res) { 266 | should(error).be.equal(null) 267 | should(res).be.equal('OK') 268 | return this.get('key3') 269 | })(function (error, res) { 270 | should(error).be.equal(null) 271 | should(res).be.equal('6') 272 | return this.get('key2') 273 | })(function (error, res) { 274 | should(error).be.equal(null) 275 | should(res).be.equal('2') 276 | return this.move('key2', 0)(function (error, res) { 277 | should(error).be.instanceOf(Error) 278 | }) 279 | })(done) 280 | }) 281 | 282 | tman.it('client.object', function (done) { 283 | client.mset({ 284 | key1: 123, 285 | key2: 'hello' 286 | })(function (error, res) { 287 | should(error).be.equal(null) 288 | should(res).be.equal('OK') 289 | return this.object('refcount', 'key1') 290 | })(function (error, res) { 291 | should(error).be.equal(null) 292 | should(res >= 1).be.equal(true) 293 | return this.object('encoding', 'key1') 294 | })(function (error, res) { 295 | should(error).be.equal(null) 296 | should(res).be.equal('int') 297 | return this.object('encoding', 'key2') 298 | })(function (error, res) { 299 | should(error).be.equal(null) 300 | should(res === 'embstr' || res === 'raw').be.equal(true) 301 | return thunk.delay(1001) 302 | })(function (error, res) { 303 | should(error).be.equal(null) 304 | should(res).be.equal(undefined) 305 | return this.object('idletime', 'key1') 306 | })(function (error, res) { 307 | should(error).be.equal(null) 308 | should(res >= 1).be.equal(true) 309 | })(done) 310 | }) 311 | 312 | tman.it('client.persist', function (done) { 313 | client.set('key', 'hello')(function (error, res) { 314 | should(error).be.equal(null) 315 | should(res).be.equal('OK') 316 | return this.expire('key', 1) 317 | })(function (error, res) { 318 | should(error).be.equal(null) 319 | should(res).be.equal(1) 320 | return this.persist('key') 321 | })(function (error, res) { 322 | should(error).be.equal(null) 323 | should(res).be.equal(1) 324 | return this.persist('key') 325 | })(function (error, res) { 326 | should(error).be.equal(null) 327 | should(res).be.equal(0) 328 | return thunk.delay(1001) 329 | })(function (error, res) { 330 | should(error).be.equal(null) 331 | return this.exists('key') 332 | })(function (error, res) { 333 | should(error).be.equal(null) 334 | should(res).be.equal(1) 335 | return this.persist('key123') 336 | })(function (error, res) { 337 | should(error).be.equal(null) 338 | should(res).be.equal(0) 339 | })(done) 340 | }) 341 | 342 | tman.it('client.pexpire', function (done) { 343 | client.set('key', 123)(function (error, res) { 344 | should(error).be.equal(null) 345 | should(res).be.equal('OK') 346 | return this.exists('key') 347 | })(function (error, res) { 348 | should(error).be.equal(null) 349 | should(res).be.equal(1) 350 | return this.pexpire('key', 100) 351 | })(function (error, res) { 352 | should(error).be.equal(null) 353 | should(res).be.equal(1) 354 | return thunk.delay.call(this, 101)(function () { 355 | return this.exists('key') 356 | }) 357 | })(function (error, res) { 358 | should(error).be.equal(null) 359 | should(res).be.equal(0) 360 | return this.pexpire('key', 100) 361 | })(function (error, res) { 362 | should(error).be.equal(null) 363 | should(res).be.equal(0) 364 | })(done) 365 | }) 366 | 367 | tman.it('client.pexpireat', function (done) { 368 | client.set('key', 123)(function (error, res) { 369 | should(error).be.equal(null) 370 | should(res).be.equal('OK') 371 | return this.exists('key') 372 | })(function (error, res) { 373 | should(error).be.equal(null) 374 | should(res).be.equal(1) 375 | return this.pexpireat('key', Date.now() + 100) 376 | })(function (error, res) { 377 | should(error).be.equal(null) 378 | should(res).be.equal(1) 379 | return thunk.delay.call(this, 101)(function () { 380 | return this.exists('key') 381 | }) 382 | })(function (error, res) { 383 | should(error).be.equal(null) 384 | should(res).be.equal(0) 385 | return this.pexpireat('key', Date.now() + 100) 386 | })(function (error, res) { 387 | should(error).be.equal(null) 388 | should(res).be.equal(0) 389 | })(done) 390 | }) 391 | 392 | tman.it('client.pttl, client.ttl', function (done) { 393 | client.set('key', 'hello')(function (error, res) { 394 | should(error).be.equal(null) 395 | should(res).be.equal('OK') 396 | return this.pttl('key') 397 | })(function (error, res) { 398 | should(error).be.equal(null) 399 | should(res).be.equal(-1) 400 | return this.pttl('key123') 401 | })(function (error, res) { 402 | should(error).be.equal(null) 403 | should(res).be.equal(-2) 404 | return this.ttl('key') 405 | })(function (error, res) { 406 | should(error).be.equal(null) 407 | should(res).be.equal(-1) 408 | return this.ttl('key123') 409 | })(function (error, res) { 410 | should(error).be.equal(null) 411 | should(res).be.equal(-2) 412 | return this.exists('key') 413 | })(function (error, res) { 414 | should(error).be.equal(null) 415 | should(res).be.equal(1) 416 | return this.pexpire('key', 1200) 417 | })(function (error, res) { 418 | should(error).be.equal(null) 419 | should(res).be.equal(1) 420 | return this.ttl('key') 421 | })(function (error, res) { 422 | should(error).be.equal(null) 423 | should(res >= 1).be.equal(true) 424 | return this.pttl('key') 425 | })(function (error, res) { 426 | should(error).be.equal(null) 427 | should(res >= 1000).be.equal(true) 428 | })(done) 429 | }) 430 | 431 | tman.it('client.randomkey', function (done) { 432 | client.randomkey()(function (error, res) { 433 | should(error).be.equal(null) 434 | should(res).be.equal(null) 435 | return this.set('key', 'hello') 436 | })(function (error, res) { 437 | should(error).be.equal(null) 438 | should(res).be.equal('OK') 439 | return this.randomkey() 440 | })(function (error, res) { 441 | should(error).be.equal(null) 442 | should(res).be.equal('key') 443 | })(done) 444 | }) 445 | 446 | tman.it('client.rename, client.renamenx', function (done) { 447 | client.mset({ 448 | key: 'hello', 449 | newkey: 1 450 | })(function (error, res) { 451 | should(error).be.equal(null) 452 | should(res).be.equal('OK') 453 | return this.rename('key', 'newkey') 454 | })(function (error, res) { 455 | should(error).be.equal(null) 456 | should(res).be.equal('OK') 457 | return this.exists('key') 458 | })(function (error, res) { 459 | should(error).be.equal(null) 460 | should(res).be.equal(0) 461 | return this.get('newkey') 462 | })(function (error, res) { 463 | should(error).be.equal(null) 464 | should(res).be.equal('hello') 465 | return this.rename('key', 'key1')(function (error, res) { 466 | should(error).be.instanceOf(Error) 467 | return this.rename('newkey', 'newkey') 468 | })(function (error, res) { 469 | should(error).be.instanceOf(Error) 470 | return this.renamenx('key', 'newkey') 471 | })(function (error, res) { 472 | should(error).be.instanceOf(Error) 473 | return this.set('key', 1) 474 | }) 475 | })(function (error, res) { 476 | should(error).be.equal(null) 477 | should(res).be.equal('OK') 478 | return this.renamenx('newkey', 'key') 479 | })(function (error, res) { 480 | should(error).be.equal(null) 481 | should(res).be.equal(0) 482 | return this.renamenx('newkey', 'key1') 483 | })(function (error, res) { 484 | should(error).be.equal(null) 485 | should(res).be.equal(1) 486 | return this.get('key1') 487 | })(function (error, res) { 488 | should(error).be.equal(null) 489 | should(res).be.equal('hello') 490 | return this.exists('newkey') 491 | })(function (error, res) { 492 | should(error).be.equal(null) 493 | should(res).be.equal(0) 494 | })(done) 495 | }) 496 | 497 | tman.it('client.sort', function (done) { 498 | client.sort('key')(function (error, res) { 499 | should(error).be.equal(null) 500 | should(res).be.eql([]) 501 | return this.set('key', 12345) 502 | })(function (error, res) { 503 | should(error).be.equal(null) 504 | should(res).be.equal('OK') 505 | return this.sort('key')(function (error, res) { 506 | should(error).be.instanceOf(Error) 507 | return this.lpush('list', 1, 3, 5, 4, 2) 508 | }) 509 | })(function (error, res) { 510 | should(error).be.equal(null) 511 | should(res).be.equal(5) 512 | return this.sort('list') 513 | })(function (error, res) { 514 | should(error).be.equal(null) 515 | should(res).be.eql(['1', '2', '3', '4', '5']) 516 | return this.sort('list', 'desc') 517 | })(function (error, res) { 518 | should(error).be.equal(null) 519 | should(res).be.eql(['5', '4', '3', '2', '1']) 520 | return this.lpush('list1', 'a', 'b', 'ac') 521 | })(function (error, res) { 522 | should(error).be.equal(null) 523 | should(res).be.equal(3) 524 | return this.sort('list1', 'desc', 'alpha') 525 | })(function (error, res) { 526 | should(error).be.equal(null) 527 | should(res).be.eql(['b', 'ac', 'a']) 528 | return this.sort('list', 'desc', 'limit', '1', '3') 529 | })(function (error, res) { 530 | should(error).be.equal(null) 531 | should(res).be.eql(['4', '3', '2']) 532 | return this.mset({ 533 | user1: 80, 534 | user2: 100, 535 | user3: 90, 536 | user4: 70, 537 | user5: 95, 538 | user1name: 'zhang', 539 | user2name: 'li', 540 | user3name: 'wang', 541 | user4name: 'liu', 542 | user5name: 'yan' 543 | }) 544 | })(function (error, res) { 545 | should(error).be.equal(null) 546 | should(res).be.equal('OK') 547 | return this.sort('list', 'by', 'user*', 'get', 'user*name', 'desc') 548 | })(function (error, res) { 549 | should(error).be.equal(null) 550 | should(res).be.eql(['li', 'yan', 'wang', 'zhang', 'liu']) 551 | return this.sort('list', 'by', 'user*', 'get', 'user*name', 'store', 'sorteduser') 552 | })(function (error, res) { 553 | should(error).be.equal(null) 554 | should(res).be.equal(5) 555 | return this.lrange('sorteduser', 0, -1) 556 | })(function (error, res) { 557 | should(error).be.equal(null) 558 | should(res).be.eql(['liu', 'zhang', 'wang', 'yan', 'li']) 559 | })(done) 560 | }) 561 | 562 | tman.it('client.type', function (done) { 563 | client.mset({ 564 | a: 123, 565 | b: '123' 566 | })(function (error, res) { 567 | should(error).be.equal(null) 568 | should(res).be.equal('OK') 569 | return this.type('key') 570 | })(function (error, res) { 571 | should(error).be.equal(null) 572 | should(res).be.equal('none') 573 | return this.type('a') 574 | })(function (error, res) { 575 | should(error).be.equal(null) 576 | should(res).be.equal('string') 577 | return this.type('b') 578 | })(function (error, res) { 579 | should(error).be.equal(null) 580 | should(res).be.equal('string') 581 | return this.lpush('list', '123') 582 | })(function (error, res) { 583 | should(error).be.equal(null) 584 | should(res).be.equal(1) 585 | return this.type('list') 586 | })(function (error, res) { 587 | should(error).be.equal(null) 588 | should(res).be.equal('list') 589 | })(done) 590 | }) 591 | 592 | tman.it('client.scan', function (done) { 593 | let count = 100 594 | const data = {} 595 | let scanKeys = [] 596 | 597 | while (count--) data['key' + count] = count 598 | 599 | function fullScan (cursor) { 600 | return client.scan(cursor)(function (error, res) { 601 | should(error).be.equal(null) 602 | scanKeys = scanKeys.concat(res[1]) 603 | if (res[0] === '0') return res 604 | return fullScan(res[0]) 605 | }) 606 | } 607 | 608 | client.scan(0)(function (error, res) { 609 | should(error).be.equal(null) 610 | should(res).be.eql(['0', []]) 611 | return this.mset(data) 612 | })(function (error, res) { 613 | should(error).be.equal(null) 614 | should(res).be.equal('OK') 615 | return fullScan(0) 616 | })(function (error, res) { 617 | should(error).be.equal(null) 618 | for (const key of Object.keys(data)) { 619 | should(scanKeys.indexOf(key) >= 0).be.equal(true) 620 | } 621 | return this.scan('0', 'count', 20) 622 | })(function (error, res) { 623 | should(error).be.equal(null) 624 | should(res[0] > 0).be.equal(true) 625 | should(res[1].length > 0).be.equal(true) 626 | return this.scan('0', 'count', 200, 'match', '*0') 627 | })(function (error, res) { 628 | should(error).be.equal(null) 629 | should(res[0] === '0').be.equal(true) 630 | should(res[1].length === 10).be.equal(true) 631 | })(done) 632 | }) 633 | }) 634 | -------------------------------------------------------------------------------- /test/commands/list.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const thunk = require('thunks')() 6 | const redis = require('../..') 7 | 8 | tman.suite('commands:List', function () { 9 | let client, client1 10 | 11 | tman.before(function () { 12 | client = redis.createClient({ database: 0 }) 13 | client.on('error', function (error) { 14 | console.error('redis client:', error) 15 | }) 16 | client1 = redis.createClient({ database: 0 }) 17 | client1.on('error', function (error) { 18 | console.error('redis client1:', error) 19 | }) 20 | }) 21 | 22 | tman.beforeEach(function (done) { 23 | client.flushdb()(function (error, res) { 24 | should(error).be.equal(null) 25 | should(res).be.equal('OK') 26 | })(done) 27 | }) 28 | 29 | tman.after(function () { 30 | client.clientEnd() 31 | client1.clientEnd() 32 | }) 33 | 34 | tman.it('client.blpop, client.brpop', function (done) { 35 | const time = Date.now() 36 | thunk.all.call(client, [ 37 | client.blpop('listA', 0), 38 | thunk.delay(100)(function () { 39 | return client1.lpush('listA', 'abc') 40 | }) 41 | ])(function (error, res) { 42 | should(error).be.equal(null) 43 | should(res).be.eql([['listA', 'abc'], 1]) 44 | should((Date.now() - time) >= 98).be.equal(true) 45 | return thunk.all(this.blpop('listA', 0), client1.lpush('listA', 'abcd'), this.llen('listA')) 46 | })(function (error, res) { 47 | should(error).be.equal(null) 48 | should(res).be.eql([['listA', 'abcd'], 1, 0]) 49 | return thunk.all(this.lpush('listB', 'b', 'b1'), this.lpush('listC', 'c'), this.blpop('listA', 'listB', 'listC', 0), this.llen('listB')) 50 | })(function (error, res) { 51 | should(error).be.equal(null) 52 | should(res).be.eql([2, 1, ['listB', 'b1'], 1]) 53 | return thunk.all(this.lpush('listD', 'd', 'd1'), this.lpush('listC', 'c'), this.brpop('listA', 'listD', 'listC', 0), this.llen('listB')) 54 | })(function (error, res) { 55 | should(error).be.equal(null) 56 | should(res).be.eql([2, 2, ['listD', 'd'], 1]) 57 | })(done) 58 | }) 59 | 60 | tman.it('client.brpoplpush, client.rpoplpush', function (done) { 61 | const time = Date.now() 62 | thunk.all.call(client, [ 63 | client.brpoplpush('listA', 'listB', 0), 64 | thunk.delay(100)(function () { 65 | return client1.lpush('listA', 'abc') 66 | }) 67 | ])(function (error, res) { 68 | should(error).be.equal(null) 69 | should(res).be.eql(['abc', 1]) 70 | should((Date.now() - time) >= 98).be.equal(true) 71 | return thunk.all(this.lpush('listB', 'b0', 'b1'), this.rpoplpush('listA', 'listB'), this.llen('listB')) 72 | })(function (error, res) { 73 | should(error).be.equal(null) 74 | should(res).be.eql([3, null, 3]) 75 | return thunk.all(this.lpush('listA', 'a0', 'a1'), this.rpoplpush('listA', 'listB'), this.lrange('listB', 0, -1)) 76 | })(function (error, res) { 77 | should(error).be.equal(null) 78 | should(res).be.eql([2, 'a0', ['a0', 'b1', 'b0', 'abc']]) 79 | return thunk.all(this.rpoplpush('listB', 'listB'), this.lrange('listB', 0, -1)) 80 | })(function (error, res) { 81 | should(error).be.equal(null) 82 | should(res).be.eql(['abc', ['abc', 'a0', 'b1', 'b0']]) 83 | })(done) 84 | }) 85 | 86 | tman.it('client.lindex, client.linsert', function (done) { 87 | client.lindex('listA', 0)(function (error, res) { 88 | should(error).be.equal(null) 89 | should(res).be.equal(null) 90 | return thunk.all(this.lpush('listA', 'a0', 'a1'), this.lindex('listA', 0), this.lindex('listA', -1)) 91 | })(function (error, res) { 92 | should(error).be.equal(null) 93 | should(res).be.eql([2, 'a1', 'a0']) 94 | return thunk.all(this.set('key', 123), this.lindex('key', 0)) 95 | })(function (error, res) { 96 | should(error).be.instanceOf(Error) 97 | return this.linsert('key', 'before', 'abc', 'edf') 98 | })(function (error, res) { 99 | should(error).be.instanceOf(Error) 100 | return this.linsert('listB', 'before', 'abc', 'edf') 101 | })(function (error, res) { 102 | should(error).be.equal(null) 103 | should(res).be.equal(0) 104 | return this.linsert('listA', 'before', 'abc', 'edf') 105 | })(function (error, res) { 106 | should(error).be.equal(null) 107 | should(res).be.equal(-1) 108 | return thunk.all(this.linsert('listA', 'before', 'a0', 'edf'), this.linsert('listA', 'after', 'a0', 'edf'), this.lrange('listA', 0, -1)) 109 | })(function (error, res) { 110 | should(error).be.equal(null) 111 | should(res).be.eql([3, 4, ['a1', 'edf', 'a0', 'edf']]) 112 | })(done) 113 | }) 114 | 115 | tman.it('client.llen, client.lpop, client.lpush', function (done) { 116 | client.llen('listA')(function (error, res) { 117 | should(error).be.equal(null) 118 | should(res).be.equal(0) 119 | return thunk.all(this.set('key', 123), this.llen('key')) 120 | })(function (error, res) { 121 | should(error).be.instanceOf(Error) 122 | return thunk.all(this.lpush('listA', 'a0', 'a1', 'a2'), this.llen('listA')) 123 | })(function (error, res) { 124 | should(error).be.equal(null) 125 | should(res).be.eql([3, 3]) 126 | return thunk.all(this.lpop('listA'), this.lpop('listA'), this.lpop('listA'), this.lpop('listA')) 127 | })(function (error, res) { 128 | should(error).be.equal(null) 129 | should(res).be.eql(['a2', 'a1', 'a0', null]) 130 | })(done) 131 | }) 132 | 133 | tman.it('client.lpushx, client.lrange, client.lrem', function (done) { 134 | client.lpushx('listA', 'a')(function (error, res) { 135 | should(error).be.equal(null) 136 | should(res).be.equal(0) 137 | return thunk.all(this.set('key', 123), this.lpushx('key', 'a')) 138 | })(function (error, res) { 139 | should(error).be.instanceOf(Error) 140 | return thunk.all(this.lpush('listA', 'a0'), this.lpushx('listA', 'a1')) 141 | })(function (error, res) { 142 | should(error).be.equal(null) 143 | should(res).be.eql([1, 2]) 144 | return thunk.all(this.lrange('listA', 0, -1), this.lrange('listB', 0, -1)) 145 | })(function (error, res) { 146 | should(error).be.equal(null) 147 | should(res).be.eql([['a1', 'a0'], []]) 148 | return thunk.all(this.lrem('listA', 0, 'a0'), this.lrem('listB', 0, 'a0')) 149 | })(function (error, res) { 150 | should(error).be.equal(null) 151 | should(res).be.eql([1, 0]) 152 | return thunk.all(this.lpush('listB', 'b0', 'b1', 'b', 'b1', 'b2'), this.lrem('listB', 0, 'b1'), this.lrange('listB', 0, -1)) 153 | })(function (error, res) { 154 | should(error).be.equal(null) 155 | should(res).be.eql([5, 2, ['b2', 'b', 'b0']]) 156 | })(done) 157 | }) 158 | 159 | tman.it('client.lset, client.ltrim', function (done) { 160 | client.lset('listA', 0, 'a')(function (error, res) { 161 | should(error).be.instanceOf(Error) 162 | return thunk.all(this.lpush('listA', 'a'), this.lset('listA', 1, 'a')) 163 | })(function (error, res) { 164 | should(error).be.instanceOf(Error) 165 | return thunk.all(this.lpush('listA', 'b'), this.lset('listA', 1, 'a1'), this.lrange('listA', 0, -1)) 166 | })(function (error, res) { 167 | should(error).be.equal(null) 168 | should(res).be.eql([2, 'OK', ['b', 'a1']]) 169 | return thunk.all(this.ltrim('listA', 0, 0), this.ltrim('listB', 0, 0), this.lrange('listA', 0, -1)) 170 | })(function (error, res) { 171 | should(error).be.equal(null) 172 | should(res).be.eql(['OK', 'OK', ['b']]) 173 | return thunk.all(this.set('key', 'a'), this.ltrim('key', 0, 0)) 174 | })(function (error, res) { 175 | should(error).be.instanceOf(Error) 176 | })(done) 177 | }) 178 | 179 | tman.it('client.rpop, client.rpush, client.rpushx', function (done) { 180 | client.rpop('listA')(function (error, res) { 181 | should(error).be.equal(null) 182 | should(res).be.equal(null) 183 | return thunk.all(this.set('key', 123), this.rpop('key')) 184 | })(function (error, res) { 185 | should(error).be.instanceOf(Error) 186 | return thunk.all(this.rpush('listA', 'a0', 'a1', 'a2'), this.rpop('listA')) 187 | })(function (error, res) { 188 | should(error).be.equal(null) 189 | should(res).be.eql([3, 'a2']) 190 | return thunk.all(this.rpushx('listA', 'a3'), this.rpushx('listB', 'a3')) 191 | })(function (error, res) { 192 | should(error).be.equal(null) 193 | should(res).be.eql([3, 0]) 194 | })(done) 195 | }) 196 | }) 197 | -------------------------------------------------------------------------------- /test/commands/pubsub.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const thunk = require('thunks')() 6 | const redis = require('../..') 7 | 8 | tman.suite('commands:Pubsub', function () { 9 | let client1, client2, client3 10 | 11 | tman.beforeEach(function (done) { 12 | client1 = redis.createClient() 13 | client1.on('error', function (error) { 14 | console.error('redis client:', error) 15 | }) 16 | 17 | client2 = redis.createClient() 18 | client2.on('error', function (error) { 19 | console.error('redis client:', error) 20 | }) 21 | 22 | client3 = redis.createClient() 23 | client3.on('error', function (error) { 24 | console.error('redis client:', error) 25 | }) 26 | 27 | client1.flushdb()(function (error, res) { 28 | should(error).be.equal(null) 29 | should(res).be.equal('OK') 30 | })(done) 31 | }) 32 | 33 | tman.afterEach(function () { 34 | client1.clientEnd() 35 | client2.clientEnd() 36 | client3.clientEnd() 37 | }) 38 | 39 | tman.it('client.psubscribe, client.punsubscribe', function (done) { 40 | client1 41 | .on('psubscribe', function (pattern, n) { 42 | if (pattern === 'a.*') should(n).be.equal(1) 43 | if (pattern === 'b.*') should(n).be.equal(2) 44 | if (pattern === '123') should(n).be.equal(3) 45 | }) 46 | .on('punsubscribe', function (pattern, n) { 47 | if (pattern === 'a.*') should(n).be.equal(2) 48 | if (pattern === 'b.*') should(n).be.equal(1) 49 | if (pattern === '123') { 50 | should(n).be.equal(0) 51 | done() 52 | } 53 | }) 54 | client1.psubscribe()(function (error, res) { 55 | should(error).be.instanceOf(Error) 56 | should(res).be.equal(undefined) 57 | }) 58 | client1.psubscribe('a.*', 'b.*', '123')(function (error, res) { 59 | should(error).be.equal(null) 60 | should(res).be.equal(undefined) 61 | return this.punsubscribe() 62 | })(function (error, res) { 63 | should(error).be.equal(null) 64 | should(res).be.equal(undefined) 65 | }) 66 | }) 67 | 68 | tman.it('client.subscribe, client.unsubscribe', function (done) { 69 | client1 70 | .on('subscribe', function (pattern, n) { 71 | if (pattern === 'a') should(n).be.equal(1) 72 | if (pattern === 'b') should(n).be.equal(2) 73 | if (pattern === '123') should(n).be.equal(3) 74 | }) 75 | .on('unsubscribe', function (pattern, n) { 76 | if (pattern === 'a') should(n).be.equal(2) 77 | if (pattern === '*') should(n).be.equal(2) 78 | if (pattern === '123') should(n).be.equal(1) 79 | if (pattern === 'b') { 80 | should(n).be.equal(0) 81 | done() 82 | } 83 | }) 84 | client1.subscribe()(function (error, res) { 85 | should(error).be.instanceOf(Error) 86 | should(res).be.equal(undefined) 87 | }) 88 | client1.subscribe('a', 'b', '123')(function (error, res) { 89 | should(error).be.equal(null) 90 | should(res).be.equal(undefined) 91 | return this.unsubscribe('a', '*', '123', 'b') 92 | })(function (error, res) { 93 | should(error).be.equal(null) 94 | should(res).be.equal(undefined) 95 | }) 96 | }) 97 | 98 | tman.it('client.publish', function (done) { 99 | const messages = [] 100 | client1 101 | .on('message', function (channel, message) { 102 | messages.push(message) 103 | }) 104 | .on('pmessage', function (pattern, channel, message) { 105 | messages.push(message) 106 | if (message === 'end') { 107 | should(messages).be.eql(['hello1', 'hello2', 'hello2', 'end']) 108 | thunk.delay(10)(done) 109 | } 110 | }) 111 | client2.publish()(function (error, res) { 112 | should(error).be.instanceOf(Error) 113 | should(res).be.equal(undefined) 114 | return this.publish('a', 'hello') 115 | })(function (error, res) { 116 | should(error).be.equal(null) 117 | should(res).be.equal(0) 118 | return client1.subscribe('a') 119 | })(function (error, res) { 120 | should(error).be.equal(null) 121 | should(res).be.equal(undefined) 122 | return this.publish('a', 'hello1') 123 | })(function (error, res) { 124 | should(error).be.equal(null) 125 | should(res).be.equal(1) 126 | return client1.psubscribe('*') 127 | })(function (error, res) { 128 | should(error).be.equal(null) 129 | should(res).be.equal(undefined) 130 | return this.publish('a', 'hello2') 131 | })(function (error, res) { 132 | should(error).be.equal(null) 133 | should(res).be.equal(2) 134 | return this.publish('b', 'end') 135 | })(function (error, res) { 136 | should(error).be.equal(null) 137 | should(res).be.equal(1) 138 | }) 139 | }) 140 | 141 | tman.it('client.pubsub', function (done) { 142 | thunk.call(client3, client1.subscribe('a', 'b', 'ab'))(function (error, res) { 143 | should(error).be.equal(null) 144 | should(res).be.equal(undefined) 145 | return this.pubsub('channels') 146 | })(function (error, res) { 147 | should(error).be.equal(null) 148 | should(res.length).be.equal(3) 149 | should(res).be.containEql('a') 150 | should(res).be.containEql('ab') 151 | should(res).be.containEql('b') 152 | return client2.subscribe('b', 'ab', 'abc') 153 | })(function (error, res) { 154 | should(error).be.equal(null) 155 | should(res).be.equal(undefined) 156 | return this.pubsub('channels', 'a*') 157 | })(function (error, res) { 158 | should(error).be.equal(null) 159 | should(res.length).be.equal(3) 160 | should(res).be.containEql('a') 161 | should(res).be.containEql('ab') 162 | should(res).be.containEql('abc') 163 | return thunk.all(this.pubsub('numsub'), this.pubsub('numsub', 'a', 'b', 'ab', 'd')) 164 | })(function (error, res) { 165 | should(error).be.equal(null) 166 | should(res[0]).be.eql({}) 167 | should(+res[1].a).be.equal(1) 168 | should(+res[1].b).be.equal(2) 169 | should(+res[1].ab).be.equal(2) 170 | should(+res[1].d).be.equal(0) 171 | return this.pubsub('numpat') 172 | })(function (error, res) { 173 | should(error).be.equal(null) 174 | should(res).be.equal(0) 175 | return thunk.all(client1.psubscribe('a.*', 'b.*', '123'), client2.psubscribe('a.*', 'b.*', '456')) 176 | })(function (error, res) { 177 | should(error).be.equal(null) 178 | should(res).be.eql([undefined, undefined]) 179 | return this.pubsub('numpat') 180 | })(function (error, res) { 181 | should(error).be.equal(null) 182 | should(res).be.equal(6) 183 | })(done) 184 | }) 185 | }) 186 | -------------------------------------------------------------------------------- /test/commands/script.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const thunk = require('thunks')() 6 | const redis = require('../..') 7 | 8 | tman.suite('commands:Script', function () { 9 | let client 10 | 11 | tman.before(function () { 12 | client = redis.createClient({ 13 | database: 0 14 | }) 15 | client.on('error', function (error) { 16 | console.error('redis client:', error) 17 | }) 18 | }) 19 | 20 | tman.beforeEach(function (done) { 21 | client.flushdb()(function (error, res) { 22 | should(error).be.equal(null) 23 | should(res).be.equal('OK') 24 | })(done) 25 | }) 26 | 27 | tman.after(function () { 28 | client.clientEnd() 29 | }) 30 | 31 | tman.it('client.eval', function (done) { 32 | client.eval('return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', 2, 'key1', 'key2', 'first', 'second')(function (error, res) { 33 | should(error).be.equal(null) 34 | should(res).be.eql(['key1', 'key2', 'first', 'second']) 35 | return this.eval('return redis.call("set",KEYS[1],"bar")', 1, 'foo') 36 | })(function (error, res) { 37 | should(error).be.equal(null) 38 | should(res).be.equal('OK') 39 | return thunk.all(this.get('foo'), this.eval('return redis.call("get","foo")', 0)) 40 | })(function (error, res) { 41 | should(error).be.equal(null) 42 | should(res).be.eql(['bar', 'bar']) 43 | return thunk.all(this.lpush('list', 123), this.eval('return redis.call("get", "list")', 0)) 44 | })(function (error, res) { 45 | should(error).be.instanceOf(Error) 46 | return this.eval('return redis.pcall("get", "list")', 0) 47 | })(function (error, res) { 48 | should(error).be.instanceOf(Error) 49 | })(done) 50 | }) 51 | 52 | tman.it('client.script, client.evalsha', function (done) { 53 | let sha = null 54 | 55 | client.script('load', 'return "hello thunk-redis"')(function (error, res) { 56 | should(error).be.equal(null) 57 | sha = res 58 | return this.evalsha(res, 0) 59 | })(function (error, res) { 60 | should(error).be.equal(null) 61 | should(res).be.equal('hello thunk-redis') 62 | return this.script('exists', sha) 63 | })(function (error, res) { 64 | should(error).be.equal(null) 65 | should(res).be.eql([1]) 66 | return thunk.all(this.script('flush'), this.script('exists', sha)) 67 | })(function (error, res) { 68 | should(error).be.equal(null) 69 | should(res).be.eql(['OK', [0]]) 70 | return this.script('kill') 71 | })(function (error, res) { 72 | should(error).be.instanceOf(Error) 73 | })(done) 74 | }) 75 | 76 | tman.it('client.evalauto', function (done) { 77 | client.evalauto('return {KEYS[1],ARGV[1],ARGV[2]}', 1, 'key1', 'first', 'second')(function (error, res) { 78 | should(error).be.equal(null) 79 | should(res).be.eql(['key1', 'first', 'second']) 80 | return this.evalauto('return redis.call("set",KEYS[1],"bar")', 1, 'foo') 81 | })(function (error, res) { 82 | should(error).be.equal(null) 83 | should(res).be.equal('OK') 84 | return thunk.all(this.get('foo'), this.evalauto('return redis.call("get","foo")', 0)) 85 | })(function (error, res) { 86 | should(error).be.equal(null) 87 | should(res).be.eql(['bar', 'bar']) 88 | return thunk.all(this.lpush('list', 123), this.evalauto('return redis.call("get", "list")', 0)) 89 | })(function (error, res) { 90 | should(error).be.instanceOf(Error) 91 | return this.evalauto('return redis.pcall("get", "list")', 0) 92 | })(function (error, res) { 93 | should(error).be.instanceOf(Error) 94 | })(done) 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /test/commands/server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const thunk = require('thunks')() 6 | const redis = require('../..') 7 | 8 | tman.suite('commands:Server', function () { 9 | let client1, client2, client3, client4 10 | 11 | tman.beforeEach(function (done) { 12 | client1 = redis.createClient() 13 | client1.on('error', function (error) { 14 | console.error('redis client:', error) 15 | }) 16 | 17 | client2 = redis.createClient() 18 | client2.on('error', function (error) { 19 | console.error('redis client:', error) 20 | }) 21 | 22 | client3 = redis.createClient() 23 | client3.on('error', function (error) { 24 | console.error('redis client:', error) 25 | }) 26 | 27 | client1.flushall()(function (error, res) { 28 | should(error).be.equal(null) 29 | should(res).be.equal('OK') 30 | })(done) 31 | }) 32 | 33 | tman.afterEach(function () { 34 | client1.clientEnd() 35 | client2.clientEnd() 36 | client3.clientEnd() 37 | }) 38 | 39 | tman.it('client.monitor', function (done) { 40 | client2.monitor()(function (error, res) { 41 | should(error).be.equal(null) 42 | should(res).be.equal('OK') 43 | })(done) 44 | }) 45 | 46 | tman.it('client.bgrewriteaof, client.bgsave, client.lastsave', function (done) { 47 | client1.bgrewriteaof()(function (error, res) { 48 | should(error).be.equal(null) 49 | should(res).be.equal('Background append only file rewriting started') 50 | return thunk.delay.call(this, 200)(function () { 51 | return this.bgsave() 52 | }) 53 | })(function (error, res) { 54 | should(error).be.equal(null) 55 | should(res).be.equal('Background saving started') 56 | return thunk.delay.call(this, 200)(function () { 57 | return this.lastsave() 58 | }) 59 | })(function (error, res) { 60 | should(error).be.equal(null) 61 | should((Date.now() / 1000 - res) < 10).be.equal(true) 62 | })(done) 63 | }) 64 | 65 | tman.it('client.client', function (done) { 66 | client1.client('getname')(function (error, res) { 67 | should(error).be.equal(null) 68 | should(res).be.equal(null) 69 | return thunk.all(this.client('setname', 'test-redis'), this.client('getname')) 70 | })(function (error, res) { 71 | should(error).be.equal(null) 72 | should(res).be.eql(['OK', 'test-redis']) 73 | return thunk.all(this.client('setname', ''), this.client('getname')) 74 | })(function (error, res) { 75 | should(error).be.equal(null) 76 | should(res).be.eql(['OK', null]) 77 | client4 = redis.createClient() 78 | return client4.info() 79 | })(function (error, res) { 80 | should(error).be.equal(null) 81 | return this.client('list') 82 | })(function (error, res) { 83 | const list = res.trim().spltman.it('\n') 84 | should(error).be.equal(null) 85 | should(list.length > 3).be.equal(true) 86 | const addr4 = list[list.length - 1].replace(/(^.*addr=)|( fd=.*$)/g, '') 87 | return this.client('kill', addr4) 88 | })(function (error, res) { 89 | should(error).be.equal(null) 90 | should(res).be.equal('OK') 91 | return this.client('kill', '127.0.0.1:80') 92 | })(function (error, res) { 93 | should(error).be.instanceOf(Error) 94 | })(done) 95 | }) 96 | 97 | tman.it('client.config', function (done) { 98 | client1.config('get', '*')(function (error, res) { 99 | should(error).be.equal(null) 100 | should(res.length > 10).be.equal(true) 101 | return thunk.all(this.config('set', 'slowlog-max-len', 10086), this.config('get', 'slowlog-max-len')) 102 | })(function (error, res) { 103 | should(error).be.equal(null) 104 | should(res).be.eql(['OK', ['slowlog-max-len', '10086']]) 105 | return this.config('resetstat') 106 | })(function (error, res) { 107 | should(error).be.equal(null) 108 | should(res).be.equal('OK') 109 | // return this.config('rewrite') 110 | })(done) 111 | }) 112 | 113 | tman.it('client.debug', function (done) { 114 | client1.debug('object', 'key')(function (error, res) { 115 | should(error).be.instanceOf(Error) 116 | return thunk.all(this.set('key', 100), this.debug('object', 'key')) 117 | })(function (error, res) { 118 | should(error).be.equal(null) 119 | should(res[0]).be.equal('OK') 120 | should(res[1].indexOf('encoding:int') > 0).be.equal(true) 121 | // this.debug('object', 'SEGFAULT') 122 | })(done) 123 | }) 124 | 125 | tman.it('client.dbsize, client.flushall, client.flushdb', function (done) { 126 | client1.dbsize()(function (error, res) { 127 | should(error).be.equal(null) 128 | should(res).be.equal(0) 129 | return thunk.all(this.set('key1', 100), this.set('key2', 100), this.dbsize()) 130 | })(function (error, res) { 131 | should(error).be.equal(null) 132 | should(res).be.eql(['OK', 'OK', 2]) 133 | return thunk.all(this.select(1), this.set('key', 100), this.dbsize()) 134 | })(function (error, res) { 135 | should(error).be.equal(null) 136 | should(res).be.eql(['OK', 'OK', 1]) 137 | return thunk.all(this.flushall(), this.dbsize(), this.select(0), this.dbsize()) 138 | })(function (error, res) { 139 | should(error).be.equal(null) 140 | should(res).be.eql(['OK', 0, 'OK', 0]) 141 | })(done) 142 | }) 143 | 144 | tman.it('client.time, client.info, client.slowlog', function (done) { 145 | client1.time()(function (error, res) { 146 | should(error).be.equal(null) 147 | should(res.length).be.equal(2) 148 | return this.info() 149 | })(function (error, res) { 150 | should(error).be.equal(null) 151 | should(res).be.properties('redis_version', 'os', 'process_id', 'used_memory', 'connected_clients') 152 | return thunk.all(this.slowlog('len'), this.slowlog('get')) 153 | })(function (error, res) { 154 | should(error).be.equal(null) 155 | should(res[0] >= res[1].length).be.equal(true) 156 | })(done) 157 | }) 158 | 159 | tman.it('client.command', function (done) { 160 | let len = 0 161 | client1.command()(function (error, commands) { 162 | should(error).be.equal(null) 163 | len = commands.length 164 | return thunk.all.call(this, commands.map(function (command) { 165 | return client1.command('info', command[0])(function (error, res) { 166 | should(error).be.equal(null) 167 | should(res[0]).be.eql(command) 168 | return command[0] 169 | }) 170 | }))(function (error, res) { 171 | should(error).be.equal(null) 172 | should(res.length).be.equal(len) 173 | res.unshift('info') 174 | return this.command(res) 175 | })(function (error, res) { 176 | should(error).be.equal(null) 177 | should(res).be.eql(commands) 178 | return this.command('count') 179 | }) 180 | })(function (error, res) { 181 | should(error).be.equal(null) 182 | should(res).be.equal(len) 183 | })(done) 184 | }) 185 | 186 | tman.it.skip('client.psync, client.sync, client.save, client.shutdown, client.slaveof', function (done) {}) 187 | }) 188 | -------------------------------------------------------------------------------- /test/commands/set.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const thunk = require('thunks')() 6 | const redis = require('../..') 7 | 8 | tman.suite('commands:Set', function () { 9 | let client 10 | 11 | tman.before(function () { 12 | client = redis.createClient({ 13 | database: 0 14 | }) 15 | client.on('error', function (error) { 16 | console.error('redis client:', error) 17 | }) 18 | }) 19 | 20 | tman.beforeEach(function (done) { 21 | client.flushdb()(function (error, res) { 22 | should(error).be.equal(null) 23 | should(res).be.equal('OK') 24 | })(done) 25 | }) 26 | 27 | tman.after(function () { 28 | client.clientEnd() 29 | }) 30 | 31 | tman.it('client.sadd, client.scard', function (done) { 32 | client.scard('setA')(function (error, res) { 33 | should(error).be.equal(null) 34 | should(res).be.equal(0) 35 | return thunk.all(this.set('key', 'abc'), this.scard('key')) 36 | })(function (error, res) { 37 | should(error).be.instanceOf(Error) 38 | return this.sadd('key', 'a') 39 | })(function (error, res) { 40 | should(error).be.instanceOf(Error) 41 | return thunk.all(this.sadd('setA', 'a', 'b'), this.sadd('setA', 'b', 'c'), this.sadd('setA', 'a', 'c'), this.scard('setA')) 42 | })(function (error, res) { 43 | should(error).be.equal(null) 44 | should(res).be.eql([2, 1, 0, 3]) 45 | })(done) 46 | }) 47 | 48 | tman.it('client.sdiff, client.sdiffstore', function (done) { 49 | client.sdiff('setA')(function (error, res) { 50 | should(error).be.equal(null) 51 | should(res).be.eql([]) 52 | return thunk.all(this.sadd('setA', 'a', 'b', 'c'), this.sadd('setB', 'b', 'c', 'd')) 53 | })(function (error, res) { 54 | should(error).be.equal(null) 55 | should(res).be.eql([3, 3]) 56 | return thunk.all(client.sdiff('setA'), client.sdiff('setA', 'setB'), client.sdiff('setA', 'setC')) 57 | })(function (error, res) { 58 | should(error).be.equal(null) 59 | should(res[0].length).be.equal(3) 60 | should(res[0]).be.containEql('a') 61 | should(res[0]).be.containEql('b') 62 | should(res[0]).be.containEql('c') 63 | should(res[1].length).be.equal(1) 64 | should(res[1]).be.containEql('a') 65 | should(res[2].length).be.equal(3) 66 | should(res[2]).be.containEql('a') 67 | should(res[2]).be.containEql('b') 68 | should(res[2]).be.containEql('c') 69 | return thunk.all(client.sdiffstore('setC', 'setA', 'setB'), client.sdiffstore('setA', 'setA', 'setB')) 70 | })(function (error, res) { 71 | should(error).be.equal(null) 72 | should(res).be.eql([1, 1]) 73 | return thunk.all(this.scard('setA'), this.scard('setC')) 74 | })(function (error, res) { 75 | should(error).be.equal(null) 76 | should(res).be.eql([1, 1]) 77 | })(done) 78 | }) 79 | 80 | tman.it('client.sinter, client.sinterstore', function (done) { 81 | client.sinter('setA')(function (error, res) { 82 | should(error).be.equal(null) 83 | should(res).be.eql([]) 84 | return thunk.all(this.sadd('setA', 'a', 'b', 'c'), this.sadd('setB', 'b', 'c', 'd')) 85 | })(function (error, res) { 86 | should(error).be.equal(null) 87 | should(res).be.eql([3, 3]) 88 | return thunk.all(client.sinter('setA'), client.sinter('setA', 'setB'), client.sinter('setA', 'setC')) 89 | })(function (error, res) { 90 | should(error).be.equal(null) 91 | should(res[0].length).be.equal(3) 92 | should(res[0]).be.containEql('a') 93 | should(res[0]).be.containEql('b') 94 | should(res[0]).be.containEql('c') 95 | should(res[1].length).be.equal(2) 96 | should(res[1]).be.containEql('b') 97 | should(res[1]).be.containEql('c') 98 | should(res[2].length).be.equal(0) 99 | return thunk.all(client.sinterstore('setC', 'setA', 'setB'), client.sinterstore('setA', 'setA', 'setB')) 100 | })(function (error, res) { 101 | should(error).be.equal(null) 102 | should(res).be.eql([2, 2]) 103 | return thunk.all(this.scard('setA'), this.scard('setC')) 104 | })(function (error, res) { 105 | should(error).be.equal(null) 106 | should(res).be.eql([2, 2]) 107 | })(done) 108 | }) 109 | 110 | tman.it('client.sismember, client.smembers', function (done) { 111 | client.smembers('setA')(function (error, res) { 112 | should(error).be.equal(null) 113 | should(res).be.equal([]) 114 | return thunk.all(this.set('key', 'abc'), this.smembers('key')) 115 | })(function (error, res) { 116 | should(error).be.instanceOf(Error) 117 | return thunk.all(this.sadd('setA', 'a', 'b', 'c'), this.smembers('setA')) 118 | })(function (error, res) { 119 | should(error).be.equal(null) 120 | should(res[0]).be.equal(3) 121 | should(res[1].length).be.equal(3) 122 | should(res[1]).be.containEql('a') 123 | should(res[1]).be.containEql('b') 124 | should(res[1]).be.containEql('c') 125 | return thunk.all(this.sismember('setA', 'a'), this.sismember('setA', 'd'), this.sismember('setB', 'd')) 126 | })(function (error, res) { 127 | should(error).be.equal(null) 128 | should(res).be.eql([1, 0, 0]) 129 | return this.sadd('especialKey', 'pmessage', 'message') 130 | })(function (error, res) { 131 | should(error).be.equal(null) 132 | should(res).be.equal(2) 133 | return this.smembers('especialKey') 134 | })(function (error, res) { 135 | should(error).be.equal(null) 136 | should(res).be.containEql('message') 137 | should(res).be.containEql('pmessage') 138 | })(done) 139 | }) 140 | 141 | tman.it('client.smove, client.spop', function (done) { 142 | client.smove('setA', 'setB', 'a')(function (error, res) { 143 | should(error).be.equal(null) 144 | should(res).be.equal(0) 145 | return thunk.all(this.sadd('setA', 'a', 'b', 'c'), this.smove('setA', 'setB', 'a'), this.smove('setA', 'setB', 'd')) 146 | })(function (error, res) { 147 | should(error).be.equal(null) 148 | should(res).be.eql([3, 1, 0]) 149 | return thunk.all(this.sadd('setB', 'b'), this.smove('setA', 'setB', 'b')) 150 | })(function (error, res) { 151 | should(error).be.equal(null) 152 | should(res).be.eql([1, 1]) 153 | return thunk.all(this.spop('setA'), this.spop('setC')) 154 | })(function (error, res) { 155 | should(error).be.equal(null) 156 | should(res).be.eql(['c', null]) 157 | })(done) 158 | }) 159 | 160 | tman.it('client.srandmember, client.srem', function (done) { 161 | client.srandmember('setA')(function (error, res) { 162 | should(error).be.equal(null) 163 | should(res).be.equal(null) 164 | return thunk.all(this.sadd('setA', 'a', 'b', 'c'), this.srandmember('setA'), this.srandmember('setA', 2)) 165 | })(function (error, res) { 166 | should(error).be.equal(null) 167 | should(res[0]).be.eql(3) 168 | should(['a', 'b', 'c']).be.containEql(res[1]) 169 | should(res[2].length).be.equal(2) 170 | should(['a', 'b', 'c']).be.containEql(res[2][0]) 171 | should(['a', 'b', 'c']).be.containEql(res[2][1]) 172 | return thunk.all(this.scard('setA'), this.srem('setA', 'b', 'd'), this.srem('setA', 'b', 'a', 'c'), this.scard('setA')) 173 | })(function (error, res) { 174 | should(error).be.equal(null) 175 | should(res).be.eql([3, 1, 2, 0]) 176 | })(done) 177 | }) 178 | 179 | tman.it('client.sunion, client.sunionstore', function (done) { 180 | client.sunion('setA')(function (error, res) { 181 | should(error).be.equal(null) 182 | should(res).be.eql([]) 183 | return thunk.all(this.sadd('setA', 'a', 'b', 'c'), this.sadd('setB', 'b', 'c', 'd')) 184 | })(function (error, res) { 185 | should(error).be.equal(null) 186 | should(res).be.eql([3, 3]) 187 | return thunk.all(client.sunion('setA'), client.sunion('setA', 'setB'), client.sunion('setA', 'setC')) 188 | })(function (error, res) { 189 | should(error).be.equal(null) 190 | should(res[0].length).be.equal(3) 191 | should(res[0]).be.containEql('a') 192 | should(res[0]).be.containEql('b') 193 | should(res[0]).be.containEql('c') 194 | should(res[1].length).be.equal(4) 195 | should(res[1]).be.containEql('a') 196 | should(res[1]).be.containEql('b') 197 | should(res[1]).be.containEql('c') 198 | should(res[1]).be.containEql('d') 199 | should(res[2].length).be.equal(3) 200 | return thunk.all(client.sunionstore('setC', 'setA', 'setB'), client.sunionstore('setA', 'setA', 'setB')) 201 | })(function (error, res) { 202 | should(error).be.equal(null) 203 | should(res).be.eql([4, 4]) 204 | return thunk.all(this.scard('setA'), this.scard('setC')) 205 | })(function (error, res) { 206 | should(error).be.equal(null) 207 | should(res).be.eql([4, 4]) 208 | })(done) 209 | }) 210 | 211 | tman.it('client.sscan', function (done) { 212 | let count = 100 213 | const data = [] 214 | let scanKeys = [] 215 | 216 | while (count--) data.push('m' + count) 217 | 218 | function fullScan (cursor) { 219 | return client.sscan('set', cursor)(function (error, res) { 220 | should(error).be.equal(null) 221 | scanKeys = scanKeys.concat(res[1]) 222 | if (res[0] === '0') return res 223 | return fullScan(res[0]) 224 | }) 225 | } 226 | 227 | client.sscan('set', 0)(function (error, res) { 228 | should(error).be.equal(null) 229 | should(res).be.eql(['0', []]) 230 | const args = data.slice() 231 | args.unshift('set') 232 | return this.sadd.apply(this, args) 233 | })(function (error, res) { 234 | should(error).be.equal(null) 235 | should(res).be.equal(100) 236 | return fullScan(0) 237 | })(function (error, res) { 238 | should(error).be.equal(null) 239 | should(scanKeys.length).be.equal(100) 240 | for (const key of Object.keys(data)) { 241 | should(scanKeys).be.containEql(data[key]) 242 | } 243 | return this.sscan('set', 0, 'count', 20) 244 | })(function (error, res) { 245 | should(error).be.equal(null) 246 | should(res[0] > 0).be.equal(true) 247 | should(res[1].length > 0).be.equal(true) 248 | return this.sscan('set', 0, 'count', 200, 'match', '*0') 249 | })(function (error, res) { 250 | should(error).be.equal(null) 251 | should(res[0] === '0').be.equal(true) 252 | should(res[1].length === 10).be.equal(true) 253 | })(done) 254 | }) 255 | }) 256 | -------------------------------------------------------------------------------- /test/commands/sorted-set.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const thunk = require('thunks')() 6 | const redis = require('../..') 7 | 8 | tman.suite('commands:Sorted Set', function () { 9 | let client 10 | 11 | tman.before(function () { 12 | client = redis.createClient({ 13 | database: 0 14 | }) 15 | client.on('error', function (error) { 16 | console.error('redis client:', error) 17 | }) 18 | }) 19 | 20 | tman.beforeEach(function (done) { 21 | client.flushdb()(function (error, res) { 22 | should(error).be.equal(null) 23 | should(res).be.equal('OK') 24 | })(done) 25 | }) 26 | 27 | tman.after(function () { 28 | client.clientEnd() 29 | }) 30 | 31 | tman.it('client.zadd, client.zcard, client.zcount', function (done) { 32 | client.zcard('zsetA')(function (error, res) { 33 | should(error).be.equal(null) 34 | should(res).be.equal(0) 35 | return thunk.all(this.set('key', 'abc'), this.zcard('key')) 36 | })(function (error, res) { 37 | should(error).be.instanceOf(Error) 38 | return this.zadd('key', 0, 'a') 39 | })(function (error, res) { 40 | should(error).be.instanceOf(Error) 41 | return thunk.all(this.zadd('zsetA', 0, 'a', 1, 'b'), this.zadd('zsetA', 2, 'b', 3, 'c'), this.zcard('zsetA')) 42 | })(function (error, res) { 43 | should(error).be.equal(null) 44 | should(res).be.eql([2, 1, 3]) 45 | return this.zcount('zsetA', 2, 3) 46 | })(function (error, res) { 47 | should(error).be.equal(null) 48 | should(res).be.equal(2) 49 | })(done) 50 | }) 51 | 52 | tman.it('client.zincrby, client.zscore, client.zrange, client.zrangebyscore', function (done) { 53 | client.zadd('zsetA', 2, 'a')(function (error, res) { 54 | should(error).be.equal(null) 55 | should(res).be.equal(1) 56 | return thunk.all(this.zincrby('zsetA', 10, 'a'), this.zscore('zsetA', 'a')) 57 | })(function (error, res) { 58 | should(error).be.equal(null) 59 | should(res).be.eql(['12', '12']) 60 | return thunk.all(this.zincrby('zsetA', 10, 'b'), this.zincrby('zsetA', -2, 'a'), this.zscore('zsetA', 'b')) 61 | })(function (error, res) { 62 | should(error).be.equal(null) 63 | should(res).be.eql(['10', '10', '10']) 64 | return this.zrange('zsetA', 0, -1) 65 | })(function (error, res) { 66 | should(error).be.equal(null) 67 | should(res).be.eql(['a', 'b']) 68 | return thunk.all(this.zincrby('zsetA', 15, 'c'), this.zincrby('zsetA', 10, 'b'), this.zrange('zsetA', 1, -1, 'WITHSCORES')) 69 | })(function (error, res) { 70 | should(error).be.equal(null) 71 | should(res).be.eql(['15', '20', ['c', '15', 'b', '20']]) 72 | return this.zrangebyscore('zsetA', '(10', 100, 'WITHSCORES') 73 | })(function (error, res) { 74 | should(error).be.equal(null) 75 | should(res).be.eql(['c', '15', 'b', '20']) 76 | return this.zrangebyscore('zsetA', '-inf', '+inf', 'LIMIT', 1, 1) 77 | })(function (error, res) { 78 | should(error).be.equal(null) 79 | should(res).be.eql(['c']) 80 | })(done) 81 | }) 82 | 83 | tman.it('client.zrank, client.zrevrank', function (done) { 84 | client.zadd('zsetA', 1, 'a', 2, 'b', 3, 'c')(function (error, res) { 85 | should(error).be.equal(null) 86 | should(res).be.equal(3) 87 | return thunk.all(this.zrank('zsetA', 'a'), this.zrank('zsetA', 'c'), this.zrank('zsetA', 'x')) 88 | })(function (error, res) { 89 | should(error).be.equal(null) 90 | should(res).be.eql([0, 2, null]) 91 | return thunk.all(this.zrevrank('zsetA', 'a'), this.zrevrank('zsetA', 'c'), this.zrevrank('zsetA', 'x')) 92 | })(function (error, res) { 93 | should(error).be.equal(null) 94 | should(res).be.eql([2, 0, null]) 95 | })(done) 96 | }) 97 | 98 | tman.it('client.zrem, client.zremrangebyrank, client.zremrangebyscore', function (done) { 99 | client.zadd('zsetA', 1, 'a', 2, 'b', 3, 'c')(function (error, res) { 100 | should(error).be.equal(null) 101 | should(res).be.equal(3) 102 | return thunk.all(this.zrem('zsetA', 'a'), this.zrem('zsetA', 'a', 'c'), client.zadd('zsetA', 1, 'a', 2, 'b', 3, 'c')) 103 | })(function (error, res) { 104 | should(error).be.equal(null) 105 | should(res).be.eql([1, 1, 2]) 106 | return thunk.all(this.zremrangebyrank('zsetA', 1, 2), this.zrange('zsetA', 0, -1)) 107 | })(function (error, res) { 108 | should(error).be.equal(null) 109 | should(res).be.eql([2, ['a']]) 110 | return this.zadd('zsetA', 2, 'b', 3, 'c', 4, 'd') 111 | })(function (error, res) { 112 | should(error).be.equal(null) 113 | should(res).be.equal(3) 114 | return thunk.all(this.zremrangebyscore('zsetA', 2, 3), this.zrange('zsetA', 0, -1)) 115 | })(function (error, res) { 116 | should(error).be.equal(null) 117 | should(res).be.eql([2, ['a', 'd']]) 118 | })(done) 119 | }) 120 | 121 | tman.it('client.zrevrange, client.zrevrangebyscore', function (done) { 122 | client.zadd('zsetA', 1, 'a', 2, 'b', 3, 'c')(function (error, res) { 123 | should(error).be.equal(null) 124 | should(res).be.equal(3) 125 | return this.zrevrange('zsetA', 1, 100, 'WITHSCORES') 126 | })(function (error, res) { 127 | should(error).be.equal(null) 128 | should(res).be.eql(['b', '2', 'a', '1']) 129 | return this.zrevrangebyscore('zsetA', '+inf', '-inf', 'LIMIT', 1, 2) 130 | })(function (error, res) { 131 | should(error).be.equal(null) 132 | should(res).be.eql(['b', 'a']) 133 | })(done) 134 | }) 135 | 136 | tman.it('client.zunionstore, client.zinterstore', function (done) { 137 | client.zadd('zsetA', 1, 'a', 2, 'b', 3, 'c')(function (error, res) { 138 | should(error).be.equal(null) 139 | should(res).be.equal(3) 140 | return client.zadd('zsetB', 4, 'b', 5, 'c', 6, 'd') 141 | })(function (error, res) { 142 | should(error).be.equal(null) 143 | should(res).be.equal(3) 144 | return this.zunionstore('zsetU', 2, 'zsetA', 'zsetB', 'WEIGHTS', 2, 1, 'AGGREGATE', 'MAX') 145 | })(function (error, res) { 146 | should(error).be.equal(null) 147 | should(res).be.equal(4) 148 | return this.zrange('zsetU', 0, 100, 'WITHSCORES') 149 | })(function (error, res) { 150 | should(error).be.equal(null) 151 | should(res).be.eql(['a', '2', 'b', '4', 'c', '6', 'd', '6']) 152 | return this.zinterstore('zsetI', 2, 'zsetA', 'zsetB', 'WEIGHTS', 1, 2) 153 | })(function (error, res) { 154 | should(error).be.equal(null) 155 | should(res).be.equal(2) 156 | return this.zrange('zsetI', 0, 100, 'WITHSCORES') 157 | })(function (error, res) { 158 | should(error).be.equal(null) 159 | should(res).be.eql(['b', '10', 'c', '13']) 160 | })(done) 161 | }) 162 | 163 | tman.it('client.zrangebylex, client.zlexcount, client.zremrangebylex', function (done) { 164 | client.zadd('zsetA', 1, 'a', 1, 'b', 1, 'c', 1, 'bc')(function (error, res) { 165 | should(error).be.equal(null) 166 | should(res).be.equal(4) 167 | return client.zrangebylex('zsetA', '[b', '[c') 168 | })(function (error, res) { 169 | should(error).be.equal(null) 170 | should(res).be.eql(['b', 'bc', 'c']) 171 | return client.zlexcount('zsetA', '[b', '[c') 172 | })(function (error, res) { 173 | should(error).be.equal(null) 174 | should(res).be.equal(3) 175 | return client.zremrangebylex('zsetA', '[b', '[c') 176 | })(function (error, res) { 177 | should(error).be.equal(null) 178 | should(res).be.equal(3) 179 | return client.zrange('zsetA', 0, 100, 'WITHSCORES') 180 | })(function (error, res) { 181 | should(error).be.equal(null) 182 | should(res).be.eql(['a', '1']) 183 | })(done) 184 | }) 185 | 186 | tman.it('client.zscan', function (done) { 187 | let count = 100 188 | const data = [] 189 | let scanKeys = [] 190 | 191 | while (count--) data.push(count, 'z' + count) 192 | 193 | function fullScan (cursor) { 194 | return client.zscan('zset', cursor)(function (error, res) { 195 | should(error).be.equal(null) 196 | scanKeys = scanKeys.concat(res[1]) 197 | if (res[0] === '0') return res 198 | return fullScan(res[0]) 199 | }) 200 | } 201 | 202 | client.zscan('zset', 0)(function (error, res) { 203 | should(error).be.equal(null) 204 | should(res).be.eql(['0', []]) 205 | const args = data.slice() 206 | args.unshift('zset') 207 | return this.zadd.apply(this, args) 208 | })(function (error, res) { 209 | should(error).be.equal(null) 210 | should(res).be.equal(100) 211 | return fullScan(0) 212 | })(function (error, res) { 213 | should(error).be.equal(null) 214 | should(scanKeys.length).be.equal(200) 215 | for (const key of Object.keys(data)) { 216 | should(scanKeys).be.containEql(data[key] + '') 217 | } 218 | return this.zscan('zset', 0, 'match', '*0', 'COUNT', 200) 219 | })(function (error, res) { 220 | should(error).be.equal(null) 221 | should(res[0] === '0').be.equal(true) 222 | should(res[1].length === 20).be.equal(true) 223 | })(done) 224 | }) 225 | }) 226 | -------------------------------------------------------------------------------- /test/commands/string.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const redis = require('../..') 6 | 7 | tman.suite('commands:String', function () { 8 | let client 9 | 10 | tman.before(function () { 11 | client = redis.createClient({ 12 | database: 0 13 | }) 14 | client.on('error', function (error) { 15 | console.error('redis client:', error) 16 | }) 17 | }) 18 | 19 | tman.beforeEach(function (done) { 20 | client.flushdb()(function (error, res) { 21 | should(error).be.equal(null) 22 | should(res).be.equal('OK') 23 | })(done) 24 | }) 25 | 26 | tman.after(function () { 27 | client.clientEnd() 28 | }) 29 | 30 | tman.it('client.append', function (done) { 31 | client.append('key', 123)(function (error, res) { 32 | should(error).be.equal(null) 33 | should(res).be.equal(3) 34 | return this.append('key', 456) 35 | })(function (error, res) { 36 | should(error).be.equal(null) 37 | should(res).be.equal(6) 38 | return this.get('key') 39 | })(function (error, res) { 40 | should(error).be.equal(null) 41 | should(res).be.equal('123456') 42 | })(done) 43 | }) 44 | 45 | tman.it('client.bitcount, client.getbit, client.setbit', function (done) { 46 | client.getbit('key', 9)(function (error, res) { 47 | should(error).be.equal(null) 48 | should(res).be.equal(0) 49 | return this.setbit('key', 9, 1) 50 | })(function (error, res) { 51 | should(error).be.equal(null) 52 | should(res).be.equal(0) 53 | return this.getbit('key', 9) 54 | })(function (error, res) { 55 | should(error).be.equal(null) 56 | should(res).be.equal(1) 57 | return this.setbit('key', 9, 0) 58 | })(function (error, res) { 59 | should(error).be.equal(null) 60 | should(res).be.equal(1) 61 | return this.bitcount('key', 1, 2) 62 | })(function (error, res) { 63 | should(error).be.equal(null) 64 | should(res).be.equal(0) 65 | return this.del('key') 66 | })(function (error, res) { 67 | should(error).be.equal(null) 68 | should(res).be.equal(1) 69 | return this.bitcount('key') 70 | })(function (error, res) { 71 | should(error).be.equal(null) 72 | should(res).be.equal(0) 73 | return this.setbit('key', 0, 1) 74 | })(function (error, res) { 75 | should(error).be.equal(null) 76 | should(res).be.equal(0) 77 | return this.setbit('key', 3, 1) 78 | })(function (error, res) { 79 | should(error).be.equal(null) 80 | should(res).be.equal(0) 81 | return this.bitcount('key') 82 | })(function (error, res) { 83 | should(error).be.equal(null) 84 | should(res).be.equal(2) 85 | return this.bitcount('key', 1, 2) 86 | })(function (error, res) { 87 | should(error).be.equal(null) 88 | should(res).be.equal(0) 89 | })(done) 90 | }) 91 | 92 | tman.it('client.bitop', function (done) { 93 | client.bitop('or', 'key', 'key1', 'key2', 'key3')(function (error, res) { 94 | should(error).be.equal(null) 95 | should(res).be.equal(0) 96 | return this.setbit('key1', 0, 1) 97 | })(function (error, res) { 98 | should(error).be.equal(null) 99 | should(res).be.equal(0) 100 | return this.setbit('key2', 1, 1) 101 | })(function (error, res) { 102 | should(error).be.equal(null) 103 | should(res).be.equal(0) 104 | return this.setbit('key3', 2, 1) 105 | })(function (error, res) { 106 | should(error).be.equal(null) 107 | should(res).be.equal(0) 108 | return this.bitop('or', 'key', 'key1', 'key2', 'key3') 109 | })(function (error, res) { 110 | should(error).be.equal(null) 111 | should(res).be.equal(1) 112 | return this.getbit('key', 2) 113 | })(function (error, res) { 114 | should(error).be.equal(null) 115 | should(res).be.equal(1) 116 | return this.bitop('and', 'key', 'key1', 'key2', 'key3') 117 | })(function (error, res) { 118 | should(error).be.equal(null) 119 | should(res).be.equal(1) 120 | return this.bitop('xor', 'key', 'key1', 'key2', 'key3') 121 | })(function (error, res) { 122 | should(error).be.equal(null) 123 | should(res).be.equal(1) 124 | return this.bitop('not', 'key', 'key1') 125 | })(function (error, res) { 126 | should(error).be.equal(null) 127 | should(res).be.equal(1) 128 | })(done) 129 | }) 130 | 131 | tman.it('client.bitpos', function (done) { 132 | client.set('key', '\xff\xf0\x00')(function (error, res) { 133 | should(error).be.equal(null) 134 | should(res).be.equal('OK') 135 | return this.bitpos('key', 0) 136 | })(function (error, res) { 137 | should(error).be.equal(null) 138 | should(res).be.equal(2) 139 | return this.set('key2', '\x00\xff\xf0') 140 | })(function (error, res) { 141 | should(error).be.equal(null) 142 | should(res).be.equal('OK') 143 | return this.bitpos('key2', 1, 0) 144 | })(function (error, res) { 145 | should(error).be.equal(null) 146 | should(res).be.equal(8) 147 | return this.bitpos('key2', 1, 2) 148 | })(function (error, res) { 149 | should(error).be.equal(null) 150 | should(res).be.equal(16) 151 | return this.set('key3', '\x00\x00\x00') 152 | })(function (error, res) { 153 | should(error).be.equal(null) 154 | should(res).be.equal('OK') 155 | return this.bitpos('key3', 1) 156 | })(function (error, res) { 157 | should(error).be.equal(null) 158 | should(res).be.equal(-1) 159 | })(done) 160 | }) 161 | 162 | tman.it('client.decr, client.decrby, client.incr, client.incrby, client.incrbyfloat', function (done) { 163 | client.decr('key')(function (error, res) { 164 | should(error).be.equal(null) 165 | should(res).be.equal(-1) 166 | return this.decrby('key', 9) 167 | })(function (error, res) { 168 | should(error).be.equal(null) 169 | should(res).be.equal(-10) 170 | return this.incr('key') 171 | })(function (error, res) { 172 | should(error).be.equal(null) 173 | should(res).be.equal(-9) 174 | return this.incrby('key', 10) 175 | })(function (error, res) { 176 | should(error).be.equal(null) 177 | should(res).be.equal(1) 178 | return this.incrbyfloat('key', 1.1) 179 | })(function (error, res) { 180 | should(error).be.equal(null) 181 | should(res).be.equal('2.1') 182 | return this.incr('key')(function (error, res) { 183 | should(error).be.instanceOf(Error) 184 | }) 185 | })(done) 186 | }) 187 | 188 | tman.it('client.get, client.set', function (done) { 189 | client.get('key')(function (error, res) { 190 | should(error).be.equal(null) 191 | should(res).be.equal(null) 192 | return this.lpush('key', 'hello') 193 | })(function (error, res) { 194 | should(error).be.equal(null) 195 | should(res).be.equal(1) 196 | return this.get('key')(function (error, res) { 197 | should(error).be.instanceOf(Error) 198 | return this.set('key', 'hello') 199 | }) 200 | })(function (error, res) { 201 | should(error).be.equal(null) 202 | should(res).be.equal('OK') 203 | return this.get('key') 204 | })(function (error, res) { 205 | should(error).be.equal(null) 206 | should(res).be.equal('hello') 207 | return this.set('key', 123, 'nx') 208 | })(function (error, res) { 209 | should(error).be.equal(null) 210 | should(res).be.equal(null) 211 | return this.set('key1', 123, 'xx') 212 | })(function (error, res) { 213 | should(error).be.equal(null) 214 | should(res).be.equal(null) 215 | return this.set('key1', 123, 'nx', 'ex', 1) 216 | })(function (error, res) { 217 | should(error).be.equal(null) 218 | should(res).be.equal('OK') 219 | return this.set('key1', 456, 'xx', 'px', 1100) 220 | })(function (error, res) { 221 | should(error).be.equal(null) 222 | should(res).be.equal('OK') 223 | return this.pttl('key1') 224 | })(function (error, res) { 225 | should(error).be.equal(null) 226 | should(res > 1000).be.equal(true) 227 | })(done) 228 | }) 229 | 230 | tman.it('client.getset, client.getrange', function (done) { 231 | client.getset('key', 'hello')(function (error, res) { 232 | should(error).be.equal(null) 233 | should(res).be.equal(null) 234 | return this.getrange('key', 0, -2) 235 | })(function (error, res) { 236 | should(error).be.equal(null) 237 | should(res).be.equal('hell') 238 | return this.getset('key', 'world') 239 | })(function (error, res) { 240 | should(error).be.equal(null) 241 | should(res).be.equal('hello') 242 | return this.getrange('key', 1, 2) 243 | })(function (error, res) { 244 | should(error).be.equal(null) 245 | should(res).be.equal('or') 246 | return this.lpush('key1', 'hello') 247 | })(function (error, res) { 248 | should(error).be.equal(null) 249 | should(res).be.equal(1) 250 | return this.getset('key1', 'world')(function (error, res) { 251 | should(error).be.instanceOf(Error) 252 | return this.getrange('key1', 0, 10086) 253 | })(function (error, res) { 254 | should(error).be.instanceOf(Error) 255 | }) 256 | })(done) 257 | }) 258 | 259 | tman.it('client.mget, client.mset, client.msetnx', function (done) { 260 | client.mget('key1', 'key2')(function (error, res) { 261 | should(error).be.equal(null) 262 | should(res).be.eql([null, null]) 263 | return this.mset('key1', 1, 'key2', 2) 264 | })(function (error, res) { 265 | should(error).be.equal(null) 266 | should(res).be.equal('OK') 267 | return this.mget('key1', 'key3', 'key2') 268 | })(function (error, res) { 269 | should(error).be.equal(null) 270 | should(res).be.eql(['1', null, '2']) 271 | return this.mset({ 272 | key1: 0, 273 | key3: 3 274 | }) 275 | })(function (error, res) { 276 | should(error).be.equal(null) 277 | should(res).be.equal('OK') 278 | return this.mget('key1', 'key3', 'key2') 279 | })(function (error, res) { 280 | should(error).be.equal(null) 281 | should(res).be.eql(['0', '3', '2']) 282 | return this.msetnx('key3', 1, 'key4', 4) 283 | })(function (error, res) { 284 | should(error).be.equal(null) 285 | should(res).be.equal(0) 286 | return this.exists('key4') 287 | })(function (error, res) { 288 | should(error).be.equal(null) 289 | should(res).be.equal(0) 290 | return this.msetnx('key4', 4, 'key5', 5) 291 | })(function (error, res) { 292 | should(error).be.equal(null) 293 | should(res).be.equal(1) 294 | return this.msetnx({ 295 | key6: 6, 296 | key: 0 297 | }) 298 | })(function (error, res) { 299 | should(error).be.equal(null) 300 | should(res).be.equal(1) 301 | return this.mget('key', 'key5', 'key6') 302 | })(function (error, res) { 303 | should(error).be.equal(null) 304 | should(res).be.eql(['0', '5', '6']) 305 | })(done) 306 | }) 307 | 308 | tman.it('client.psetex, client.setex, client.setnx, client.setrange, client.strlen', function (done) { 309 | client.strlen('key')(function (error, res) { 310 | should(error).be.equal(null) 311 | should(res).be.equal(0) 312 | return this.lpush('key', 'hello') 313 | })(function (error, res) { 314 | should(error).be.equal(null) 315 | should(res).be.equal(1) 316 | return this.strlen('key')(function (error, res) { 317 | should(error).be.instanceOf(Error) 318 | return this.setnx('key', 'hello') 319 | }) 320 | })(function (error, res) { 321 | should(error).be.equal(null) 322 | should(res).be.equal(0) 323 | return this.setnx('key1', 'hello') 324 | })(function (error, res) { 325 | should(error).be.equal(null) 326 | should(res).be.equal(1) 327 | return this.setnx('key1', 123) 328 | })(function (error, res) { 329 | should(error).be.equal(null) 330 | should(res).be.equal(0) 331 | return this.setex('key1', 1, 456) 332 | })(function (error, res) { 333 | should(error).be.equal(null) 334 | should(res).be.equal('OK') 335 | return this.psetex('key1', 1100, 789) 336 | })(function (error, res) { 337 | should(error).be.equal(null) 338 | should(res).be.equal('OK') 339 | return this.pttl('key1') 340 | })(function (error, res) { 341 | should(error).be.equal(null) 342 | should(res > 1000).be.equal(true) 343 | return this.get('key1') 344 | })(function (error, res) { 345 | should(error).be.equal(null) 346 | should(res).be.equal('789') 347 | return this.setrange('key1', 3, '012') 348 | })(function (error, res) { 349 | should(error).be.equal(null) 350 | should(res).be.equal(6) 351 | return this.get('key1') 352 | })(function (error, res) { 353 | should(error).be.equal(null) 354 | should(res).be.equal('789012') 355 | return this.setrange('key2', 10, 'hello') 356 | })(function (error, res) { 357 | should(error).be.equal(null) 358 | should(res).be.equal(15) 359 | return this.get('key2') 360 | })(function (error, res) { 361 | should(error).be.equal(null) 362 | should(res).be.endWith('hello') 363 | })(done) 364 | }) 365 | }) 366 | -------------------------------------------------------------------------------- /test/commands/transaction.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const thunk = require('thunks')() 6 | const redis = require('../..') 7 | 8 | tman.suite('commands:Transaction', function () { 9 | let client1, client2 10 | 11 | tman.beforeEach(function (done) { 12 | client1 = redis.createClient() 13 | client1.on('error', function (error) { 14 | console.error('redis client:', error) 15 | }) 16 | 17 | client2 = redis.createClient() 18 | client2.on('error', function (error) { 19 | console.error('redis client:', error) 20 | }) 21 | 22 | client1.flushdb()(function (error, res) { 23 | should(error).be.equal(null) 24 | should(res).be.equal('OK') 25 | })(done) 26 | }) 27 | 28 | tman.afterEach(function () { 29 | client1.clientEnd() 30 | client2.clientEnd() 31 | }) 32 | 33 | tman.it('client.multi, client.discard, client.exec', function (done) { 34 | client1.multi()(function (error, res) { 35 | should(error).be.equal(null) 36 | should(res).be.equal('OK') 37 | return thunk.all(this.incr('users'), this.incr('users'), this.incr('users')) 38 | })(function (error, res) { 39 | should(error).be.equal(null) 40 | should(res).be.eql(['QUEUED', 'QUEUED', 'QUEUED']) 41 | return this.exec() 42 | })(function (error, res) { 43 | should(error).be.equal(null) 44 | should(res).be.eql(['1', '2', '3']) 45 | return this.discard() 46 | })(function (error, res) { 47 | should(error).be.instanceOf(Error) 48 | return thunk.all(this.multi(), this.ping(), this.ping(), this.discard()) 49 | })(function (error, res) { 50 | should(error).be.equal(null) 51 | should(res).be.eql(['OK', 'QUEUED', 'QUEUED', 'OK']) 52 | return this.exec() 53 | })(function (error, res) { 54 | should(error).be.instanceOf(Error) 55 | })(done) 56 | }) 57 | 58 | tman.it('client.watch, client.unwatch', function (done) { 59 | client1.watch('users')(function (error, res) { 60 | should(error).be.equal(null) 61 | should(res).be.equal('OK') 62 | return thunk.all(this.multi(), this.incr('users'), this.incr('users')) 63 | })(function (error, res) { 64 | should(error).be.equal(null) 65 | should(res).be.eql(['OK', 'QUEUED', 'QUEUED']) 66 | return client2.incr('users') 67 | })(function (error, res) { 68 | should(error).be.equal(null) 69 | should(res).be.equal(1) 70 | return this.exec() 71 | })(function (error, res) { 72 | should(error).be.equal(null) 73 | should(res).be.equal(null) 74 | return thunk.all(this.watch('i'), this.unwatch(), this.multi(), this.incr('i'), this.incr('i')) 75 | })(function (error, res) { 76 | should(error).be.equal(null) 77 | should(res).be.eql(['OK', 'OK', 'OK', 'QUEUED', 'QUEUED']) 78 | return client2.incr('i') 79 | })(function (error, res) { 80 | should(error).be.equal(null) 81 | should(res).be.equal(1) 82 | return this.exec() 83 | })(function (error, res) { 84 | should(error).be.equal(null) 85 | should(res).be.eql([2, 3]) 86 | })(done) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const should = require('should') 5 | const redis = require('..') 6 | 7 | tman.before(function * () { 8 | const cli = redis.createClient({ 9 | database: 0 10 | }) 11 | let res = yield cli.flushall() 12 | should(res).be.equal('OK') 13 | 14 | res = yield cli.dbsize() 15 | should(res).be.equal(0) 16 | 17 | res = yield cli.select(1) 18 | should(res).be.equal('OK') 19 | 20 | res = yield cli.flushdb() 21 | should(res).be.equal('OK') 22 | 23 | res = yield cli.dbsize() 24 | should(res).be.equal(0) 25 | 26 | yield cli.clientEnd() 27 | }) 28 | -------------------------------------------------------------------------------- /test/replica.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const tman = require('tman') 4 | const assert = require('assert') 5 | const thunk = require('thunks')() 6 | const redis = require('..') 7 | const clientM = redis.createClient(6390) 8 | const clientS = redis.createClient(6391, { onlyMaster: false }) 9 | 10 | clientM.on('error', function (err) { 11 | console.log('clientM', JSON.stringify(err)) 12 | }) 13 | clientS.on('error', function (err) { 14 | console.log('clientS', JSON.stringify(err)) 15 | }) 16 | 17 | tman.before(function * () { 18 | yield clientM.flushall() 19 | yield clientS.info() 20 | }) 21 | 22 | tman.after(function * () { 23 | yield thunk.delay(1000) 24 | process.exit() 25 | }) 26 | 27 | tman.suite('replication test', function () { 28 | tman.it('isMaster', function () { 29 | assert.strictEqual(clientM._redisState.getConnection(-1).isMaster, true) 30 | assert.strictEqual(clientS._redisState.getConnection(-1).isMaster, false) 31 | }) 32 | 33 | tman.it('sync keys', function * () { 34 | const value = String(Date.now()) 35 | 36 | assert.strictEqual((yield clientM.set('key1', value)), 'OK') 37 | assert.strictEqual((yield clientM.set('key2', value)), 'OK') 38 | yield thunk.delay(100) 39 | assert.strictEqual((yield clientS.get('key1')), value) 40 | assert.strictEqual((yield clientS.get('key2')), value) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/v5.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { suite, it, before, beforeEach, after } = require('tman') 4 | const { strictEqual, deepStrictEqual } = require('assert') 5 | const redis = require('..') 6 | 7 | suite('redis v5', function () { 8 | // TODO: add tests 9 | // 'xread', 'xpending', 'georadius_ro', 'xlen', 'xreadgroup', 'xadd', 'xinfo', 'xclaim', 10 | // 'bzpopmin', 'xrange', 'zpopmax', 'zpopmin', 'xgroup', 'xack', 'xtrim', 'bzpopmax', 11 | // 'xrevrange', 'georadiusbymember_ro', 'xdel' 12 | let cli 13 | 14 | before(function () { 15 | cli = redis.createClient({ 16 | database: 0 17 | }) 18 | cli.on('error', function (error) { 19 | console.error('redis client:', error) 20 | }) 21 | }) 22 | 23 | beforeEach(function * () { 24 | yield cli.flushdb() 25 | }) 26 | 27 | after(function () { 28 | cli.clientEnd() 29 | }) 30 | 31 | suite('stream', function () { 32 | it('should work', function * () { 33 | const id1 = yield cli.xadd('mystream', '*', 'name', 'Sara', 'surname', 'OConnor') 34 | const id2 = yield cli.xadd('mystream', '*', 'field1', 'value1', 'field2', 'value2', 'field3', 'value3') 35 | 36 | const len = yield cli.xlen('mystream') 37 | strictEqual(len, 2) 38 | 39 | const res = yield cli.xrange('mystream', '-', '+') 40 | strictEqual(res.length, 2) 41 | strictEqual(res[0][0], id1) 42 | deepStrictEqual(res[0][1], ['name', 'Sara', 'surname', 'OConnor']) 43 | 44 | strictEqual(res[1][0], id2) 45 | deepStrictEqual(res[1][1], ['field1', 'value1', 'field2', 'value2', 'field3', 'value3']) 46 | }) 47 | }) 48 | }) 49 | --------------------------------------------------------------------------------