付智勇

no message

正在显示 55 个修改的文件 包含 9221 行增加18 行删除
@@ -3,6 +3,8 @@ var saitMd5 = require('../util/saltMD5') @@ -3,6 +3,8 @@ var saitMd5 = require('../util/saltMD5')
3 var status = require('../util/resTemplate') 3 var status = require('../util/resTemplate')
4 const userService = require('../services/userService') 4 const userService = require('../services/userService')
5 const uuid = require('../util/UuidUtil') 5 const uuid = require('../util/UuidUtil')
  6 +const redis = require('../util/redis')
  7 +
6 8
7 9
8 var userController =function (){ 10 var userController =function (){
@@ -19,14 +21,30 @@ var userController =function (){ @@ -19,14 +21,30 @@ var userController =function (){
19 userController.prototype.addUser = async(ctx, next) =>{ 21 userController.prototype.addUser = async(ctx, next) =>{
20 try{ 22 try{
21 var params = ctx.request.body; 23 var params = ctx.request.body;
22 - const pw = saitMd5.md5AddSalt(params.password) 24 + var pw = saitMd5.md5AddSalt(params.password)
23 25
24 if(!params.loginName){ 26 if(!params.loginName){
25 return status.paramError('loginName'); 27 return status.paramError('loginName');
26 - }else if(!params.password){  
27 - return status.paramError('password','不能为空');  
28 - }else if(params.password.length < 6){  
29 - return status.paramError('password','不得小于6位'); 28 + }else if(!params.userEmail){
  29 + return status.paramError('userEmail');
  30 + }
  31 +
  32 + if(params.userRole == 2 || params.userRole == 3){
  33 + if(!params.IDcard){
  34 + return status.paramError('IDcard','不能为空');
  35 + }else if(!params.userName){
  36 + return status.paramError('userName','不能为空');
  37 + }
  38 + pw = saitMd5.md5AddSalt('123456')
  39 + params.loginName = Math.random().toString(24).substr(9);
  40 + }else if(params.userRole == 4){
  41 + if(!params.userMobile){
  42 + return status.paramError('userMobile','不能为空');
  43 + }else if(!params.password){
  44 + return status.paramError('password','不能为空');
  45 + }else if(params.password.length < 6){
  46 + return status.paramError('password','不得小于6位');
  47 + }
30 } 48 }
31 49
32 var user = { 50 var user = {
@@ -34,6 +52,7 @@ userController.prototype.addUser = async(ctx, next) =>{ @@ -34,6 +52,7 @@ userController.prototype.addUser = async(ctx, next) =>{
34 password:pw.md5Pass, 52 password:pw.md5Pass,
35 companyName:params.companyName, 53 companyName:params.companyName,
36 userName:params.userName, 54 userName:params.userName,
  55 + IDcard:params.IDcard,
37 salt:pw.salt, 56 salt:pw.salt,
38 userType:params.userType, 57 userType:params.userType,
39 userRole:params.userRole, 58 userRole:params.userRole,
@@ -54,16 +73,19 @@ userController.prototype.addUser = async(ctx, next) =>{ @@ -54,16 +73,19 @@ userController.prototype.addUser = async(ctx, next) =>{
54 * 用户登录 73 * 用户登录
55 */ 74 */
56 userController.prototype.login = async(ctx, next) =>{ 75 userController.prototype.login = async(ctx, next) =>{
  76 + try{
57 const body = ctx.request.body; 77 const body = ctx.request.body;
58 -  
59 - if(!body.name){  
60 - return status.paramError('name'); 78 + await redis.setToken('qwe123','qwe123');
  79 + let redisCode = await redis.getToken('qwe123')
  80 + console.log(redisCode)
  81 + if(redisCode != body.code ){
  82 + }else if(!body.loginName&&!body.userEmail){
  83 + return status.paramError('userEmail loginName');
61 }else if(!body.password){ 84 }else if(!body.password){
62 return status.paramError('password'); 85 return status.paramError('password');
63 } 86 }
64 87
65 - try{  
66 - let userData = await userService.login(body.name ,body.password); 88 + let userData = await userService.login(body.loginName ,body.password,body.userEmail);
67 var userBack = { 89 var userBack = {
68 id:userData.id, 90 id:userData.id,
69 loginName:userData.loginName, 91 loginName:userData.loginName,
@@ -97,6 +119,7 @@ userController.prototype.getStu = async(ctx, next) =>{ @@ -97,6 +119,7 @@ userController.prototype.getStu = async(ctx, next) =>{
97 throw new Error(error) 119 throw new Error(error)
98 } 120 }
99 } 121 }
  122 +
100 userController.prototype.updateUserByUserId = async(ctx, next) =>{ 123 userController.prototype.updateUserByUserId = async(ctx, next) =>{
101 const userId = ctx.params.userId; 124 const userId = ctx.params.userId;
102 const updateData = ctx.request.body; 125 const updateData = ctx.request.body;
@@ -147,4 +170,21 @@ userController.prototype.resetPasswordByUserId = async(ctx,next) =>{ @@ -147,4 +170,21 @@ userController.prototype.resetPasswordByUserId = async(ctx,next) =>{
147 } 170 }
148 } 171 }
149 172
  173 +userController.prototype.updatePwByTelphone = async(ctx, next) =>{
  174 + const code = ctx.request.body.code;
  175 + const telephone = ctx.request.body.telephone;
  176 + const password = ctx.request.body.password;
  177 + const pw = saitMd5.md5AddSalt(password);
  178 +
  179 + let setCode = await redis.setToken(code,code)
  180 + let redisCode = await redis.getToken(code);
  181 + if(code != redisCode){
  182 + return {code:500,msg:'验证码错误'};
  183 + }else if(!password){
  184 + return {code:501,msg:'密码不能为空'};
  185 + }
  186 + let updatePW = await userService.updatePwByTelphone(telephone,code,pw)
  187 + return {code:redisCode}
  188 +}
  189 +
150 module.exports = new userController(); 190 module.exports = new userController();
@@ -14,8 +14,10 @@ @@ -14,8 +14,10 @@
14 名称|类型|描述 14 名称|类型|描述
15 :--|:--|:-- 15 :--|:--|:--
16 |loginName |string |必须, 登录名 | 16 |loginName |string |必须, 登录名 |
17 -|password |string |必须,密码 |  
18 -|type | int |用户类型:0管理员 1监课 2老师 4学生 | 17 +|password |string |必须,密码 |
  18 +|userEmail |string |必须,邮箱 |
  19 +|userMobile |string |必须,用户手机号 |
  20 +
19 21
20 请求示例: 22 请求示例:
21 23
@@ -23,10 +25,15 @@ @@ -23,10 +25,15 @@
23 25
24 body 26 body
25 { 27 {
26 - "loginName": "admin",  
27 - "telephone": 11112112, 28 + "loginName": "尼安德特",
28 "password": "123456", 29 "password": "123456",
29 - "type":1 30 + "companyName":"bat",
  31 + "userName":"尼安德特人",
  32 + "userRole":0,
  33 + "userEmail":"kengni@buyouyu.com",
  34 + "userMobile":123124,
  35 + "content":"2017-09-01",
  36 + "groupId":1
30 } 37 }
31 返回参数: 38 返回参数:
32 39
不能预览此文件类型
  1 +var sequelize = require('../config');
  2 +var Sequelize = require('sequelize');
  3 +const uuid = require('../util/UuidUtil')
  4 +
  5 +var user = sequelize.define('3m_student_meeting', {
  6 + id: {
  7 + type: Sequelize.STRING(32),
  8 + defaultValue:uuid.db32(),
  9 + allowNull: false,
  10 + unique: true,
  11 + primaryKey: true,
  12 + field: "id"
  13 + },
  14 + studentId: {
  15 + allowNull: false,
  16 + type:Sequelize.STRING(32),
  17 + field: "student_id"
  18 + },
  19 + meetingId: {
  20 + allowNull: false,
  21 + type:Sequelize.STRING(32),
  22 + field: "meeting_id"
  23 + },
  24 +});
@@ -17,6 +17,11 @@ var user = sequelize.define('3m_user', { @@ -17,6 +17,11 @@ var user = sequelize.define('3m_user', {
17 type:Sequelize.STRING(100), 17 type:Sequelize.STRING(100),
18 field: "login_name" 18 field: "login_name"
19 }, 19 },
  20 + IDcard: {
  21 + allowNull: false,
  22 + type:Sequelize.STRING(50),
  23 + field: "IDcard"
  24 + },
20 password: { 25 password: {
21 allowNull: false, 26 allowNull: false,
22 type:Sequelize.STRING(100), 27 type:Sequelize.STRING(100),
  1 +node_modules/*
  2 +todo.txt
  3 +npm-debug.log
  4 +test/*
  5 +benchmark/*
  6 +browser/*
  7 +src/*
  8 +async
  9 +sync
  10 +mixed
  11 +bench.json
  12 +js/browser
  13 +js/browser/*
  14 +js/debug
  15 +js/debug/*
  16 +reader.js
  17 +read.txt
  18 +bench
  19 +.editorconfig
  20 +.jshintrc
  21 +ast_passes.js
  22 +mocharun.js
  23 +throwaway.js
  24 +throwaway.html
  25 +deque.sublime-workspace
  26 +deque.sublime-project
  27 +changelog.js
  28 +.travis.yml
  29 +sauce_connect.log
  30 +nodex64.exe
  31 +bump.js
  1 +"use strict";
  2 +Error.stackTraceLimit = 100;
  3 +var astPasses = require("./ast_passes.js");
  4 +
  5 +module.exports = function( grunt ) {
  6 + var isCI = !!grunt.option("ci");
  7 +
  8 + var license;
  9 + function getLicense() {
  10 + if( !license ) {
  11 + var fs = require("fs");
  12 + var text = fs.readFileSync("LICENSE", "utf8");
  13 + text = text.split("\n").map(function(line, index){
  14 + return " * " + line;
  15 + }).join("\n")
  16 + license = "/**\n" + text + "\n */\n";
  17 + }
  18 + return license
  19 + }
  20 +
  21 + function writeFile( dest, content ) {
  22 + grunt.file.write( dest, content );
  23 + grunt.log.writeln('File "' + dest + '" created.');
  24 + }
  25 +
  26 + var gruntConfig = {};
  27 +
  28 + var getGlobals = function() {
  29 + var fs = require("fs");
  30 + var file = "./src/constants.js";
  31 + var contents = fs.readFileSync(file, "utf8");
  32 + var rconstantname = /CONSTANT\(\s*([^,]+)/g;
  33 + var m;
  34 + var globals = {
  35 + "console": false,
  36 + "require": false,
  37 + "module": false,
  38 + "define": false
  39 + };
  40 + while( ( m = rconstantname.exec( contents ) ) ) {
  41 + globals[m[1]] = false;
  42 + }
  43 + return globals;
  44 + }
  45 +
  46 + gruntConfig.pkg = grunt.file.readJSON("package.json");
  47 +
  48 + gruntConfig.jshint = {
  49 + all: {
  50 + options: {
  51 + globals: getGlobals(),
  52 +
  53 + "bitwise": false,
  54 + "camelcase": true,
  55 + "curly": true,
  56 + "eqeqeq": true,
  57 + "es3": true,
  58 + "forin": true,
  59 + "immed": true,
  60 + "latedef": false,
  61 + "newcap": true,
  62 + "noarg": true,
  63 + "noempty": true,
  64 + "nonew": true,
  65 + "plusplus": false,
  66 + "quotmark": "double",
  67 + "undef": true,
  68 + "unused": true,
  69 + "strict": false,
  70 + "trailing": true,
  71 + "maxparams": 7,
  72 + "maxlen": 80,
  73 +
  74 + "asi": false,
  75 + "boss": true,
  76 + "eqnull": true,
  77 + "evil": true,
  78 + "expr": false,
  79 + "funcscope": false,
  80 + "globalstrict": false,
  81 + "lastsemic": false,
  82 + "laxcomma": false,
  83 + "laxbreak": false,
  84 + "loopfunc": true,
  85 + "multistr": true,
  86 + "proto": false,
  87 + "scripturl": true,
  88 + "smarttabs": false,
  89 + "shadow": true,
  90 + "sub": true,
  91 + "supernew": false,
  92 + "validthis": true,
  93 +
  94 + "browser": true,
  95 + "jquery": true,
  96 + "devel": true,
  97 +
  98 +
  99 + '-W014': true,
  100 + '-W116': true,
  101 + '-W106': true,
  102 + '-W064': true,
  103 + '-W097': true
  104 + },
  105 +
  106 + files: {
  107 + src: [
  108 + "./src/deque.js"
  109 + ]
  110 + }
  111 + }
  112 + };
  113 +
  114 + if( !isCI ) {
  115 + gruntConfig.jshint.all.options.reporter = require("jshint-stylish");
  116 + }
  117 +
  118 + gruntConfig.bump = {
  119 + options: {
  120 + files: ['package.json'],
  121 + updateConfigs: [],
  122 + commit: true,
  123 + commitMessage: 'Release v%VERSION%',
  124 + commitFiles: ['-a'],
  125 + createTag: true,
  126 + tagName: 'v%VERSION%',
  127 + tagMessage: 'Version %VERSION%',
  128 + false: true,
  129 + pushTo: 'master',
  130 + gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe'
  131 + }
  132 + };
  133 +
  134 + grunt.initConfig(gruntConfig);
  135 + grunt.loadNpmTasks('grunt-contrib-jshint');
  136 + grunt.loadNpmTasks('grunt-bump');
  137 +
  138 +
  139 + grunt.registerTask( "build", function() {
  140 + var fs = require("fs");
  141 + var CONSTANTS_FILE = "./src/constants.js";
  142 +
  143 + astPasses.readConstants(fs.readFileSync(CONSTANTS_FILE, "utf8"), CONSTANTS_FILE);
  144 + var fileNames = ["deque.js"];
  145 + fileNames.forEach(function(fileName){
  146 + var src = fs.readFileSync("./src/" + fileName, "utf8");
  147 + src = astPasses.removeComments(src, fileName);
  148 + src = astPasses.expandConstants(src, fileName);
  149 + src = getLicense() + src;
  150 + writeFile("./js/" + fileName, src);
  151 + });
  152 + });
  153 +
  154 + grunt.registerTask( "testrun", function() {
  155 + var fs = require("fs");
  156 + var done = this.async();
  157 + var Mocha = require("mocha");
  158 +
  159 + var mochaOpts = {
  160 + reporter: "spec",
  161 + timeout: 500,
  162 + slow: Infinity
  163 + };
  164 +
  165 + var mocha = new Mocha(mochaOpts);
  166 +
  167 + fs.readdirSync("./test").forEach(function(fileName) {
  168 + mocha.addFile("./test/" + fileName);
  169 + });
  170 +
  171 + mocha.run(function(err){
  172 + if( err ) {
  173 + process.stderr.write(test.title + "\n" + err.stack + "\n");
  174 + done(err);
  175 + }
  176 + else {
  177 + done();
  178 + }
  179 + }).on( "fail", function( test, err ) {
  180 + process.stderr.write(test.title + "\n" + err.stack + "\n");
  181 + done(err);
  182 + });
  183 + });
  184 +
  185 + grunt.registerTask( "test", ["jshint", "build", "testrun"] );
  186 + grunt.registerTask( "default", ["jshint", "build"] );
  187 +
  188 +};
  1 +Copyright (c) 2013 Petka Antonov
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +of this software and associated documentation files (the "Software"), to deal
  5 +in the Software without restriction, including without limitation the rights
  6 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7 +copies of the Software, and to permit persons to whom the Software is
  8 +furnished to do so, subject to the following conditions:</p>
  9 +
  10 +The above copyright notice and this permission notice shall be included in
  11 +all copies or substantial portions of the Software.
  12 +
  13 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19 +THE SOFTWARE.
  1 +#Introduction
  2 +
  3 +Extremely fast [double-ended queue](http://en.wikipedia.org/wiki/Double-ended_queue) implementation. Double-ended queue can also be used as a:
  4 +
  5 +- [Stack](http://en.wikipedia.org/wiki/Stack_\(abstract_data_type\))
  6 +- [Queue](http://en.wikipedia.org/wiki/Queue_\(data_structure\))
  7 +
  8 +The implementation is GC and CPU cache friendly [circular buffer](http://en.wikipedia.org/wiki/Circular_buffer). [It will run circles around any "linked list" implementation](#performance).
  9 +
  10 +Every queue operation is done in constant `O(1)` - including random access from `.get()`.
  11 +
  12 +#Topics
  13 +
  14 +- [Quick start](#quick-start)
  15 +- [Why not use an Array?](#why-not-use-an-array)
  16 +- [Using double-ended queue as a normal queue](#using-double-ended-queue-as-a-normal-queue)
  17 +- [API reference and examples](#api)
  18 +- [Performance](#performance)
  19 +
  20 +#Quick start
  21 +
  22 + npm install double-ended-queue
  23 +
  24 +```js
  25 +var Deque = require("double-ended-queue");
  26 +
  27 +var deque = new Deque([1,2,3,4]);
  28 +deque.shift(); //1
  29 +deque.pop(); //4
  30 +```
  31 +
  32 +#Why not use an Array?
  33 +
  34 +Arrays take linear `O(N)` time to do `shift` and `unshift` operations. That means in theory that an array with 1000 items is 1000x slower to do those operations than a deque with 1000 items. 10000x slower with 10000 items and so on.
  35 +
  36 +V8 implements [a trick for small arrays](https://code.google.com/p/v8/issues/detail?id=3059) where these operations are done in constant time, however even with this trick deque is still 4x faster.
  37 +
  38 +But arrays use "native" methods, they must be faster!
  39 +
  40 +In V8, there is almost no advantage for a method to be a built-in. In fact many times built-ins are at a severe disadvantage of having to implement far more complex semantics than is actually needed in practice. For example, sparse array handling punishes almost every built-in array method even though nobody uses sparse arrays as is evidenced by the popularity of the underscore library which doesn't handle sparse arrays in the same way across different browsers.
  41 +
  42 +#Using double-ended queue as a normal queue
  43 +
  44 +Queue is a more commonly needed data structure however a separate implementation does not provide any advantage in terms of performance. Aliases are provided specifically for the queue use-case. You may use `.enqueue(items...)` to enqueue item(s) and `.dequeue()` to dequeue an item.
  45 +
  46 +#API
  47 +
  48 +- [`new Deque()`](#new-deque---deque)
  49 +- [`new Deque(Array items)`](#new-dequearray-items---deque)
  50 +- [`new Deque(int capacity)`](#new-dequeint-capacity---deque)
  51 +- [`push(dynamic items...)`](#pushdynamic-items---int)
  52 +- [`unshift(dynamic items...)`](#unshiftdynamic-items---int)
  53 +- [`pop()`](#pop---dynamic)
  54 +- [`shift()`](#shift---dynamic)
  55 +- [`toArray()`](#toarray---array)
  56 +- [`peekBack()`](#peekback---dynamic)
  57 +- [`peekFront()`](#peekfront---dynamic)
  58 +- [`get(int index)`](#getint-index---dynamic)
  59 +- [`isEmpty()`](#isempty---boolean)
  60 +- [`clear()`](#clear---void)
  61 +
  62 +#####`new Deque()` -> `Deque`
  63 +
  64 +Creates an empty double-ended queue with initial capacity of 16. If you know the optimal size before-hand, use [`new Deque(int capacity)`](#new-dequeint-capacity---deque).
  65 +
  66 +```js
  67 +var deque = new Deque();
  68 +deque.push(1, 2, 3);
  69 +deque.shift(); //1
  70 +deque.pop(); //3
  71 +```
  72 +
  73 +<hr>
  74 +
  75 +#####`new Deque(Array items)` -> `Deque`
  76 +
  77 +Creates a double-ended queue from `items`.
  78 +
  79 +```js
  80 +var deque = new Deque([1,2,3,4]);
  81 +deque.shift(); //1
  82 +deque.pop(); //4
  83 +```
  84 +
  85 +<hr>
  86 +
  87 +#####`new Deque(int capacity)` -> `Deque`
  88 +
  89 +Creates an empty double-ended queue with the given `capacity`. `Capacity` should be the maximum amount of items the queue will hold at a given time.
  90 +
  91 +The reason to give an initial capacity is to avoid potentially expensive resizing operations at runtime.
  92 +
  93 +```js
  94 +var deque = new Deque(100);
  95 +deque.push(1, 2, 3);
  96 +deque.shift(); //1
  97 +deque.pop(); //3
  98 +```
  99 +
  100 +<hr>
  101 +
  102 +#####`push(dynamic items...)` -> `int`
  103 +
  104 +Push items to the back of this queue. Returns the amount of items currently in the queue after the operation.
  105 +
  106 +```js
  107 +var deque = new Deque();
  108 +deque.push(1);
  109 +deque.pop(); //1
  110 +deque.push(1, 2, 3);
  111 +deque.shift(); //1
  112 +deque.shift(); //2
  113 +deque.shift(); //3
  114 +```
  115 +
  116 +**Aliases:** `enqueue`, `insertBack`
  117 +
  118 +<hr>
  119 +
  120 +#####`unshift(dynamic items...)` -> `int`
  121 +
  122 +Unshift items to the front of this queue. Returns the amount of items currently in the queue after the operation.
  123 +
  124 +```js
  125 +var deque = new Deque([2,3]);
  126 +deque.unshift(1);
  127 +deque.toString(); //"1,2,3"
  128 +deque.unshift(-2, -1, 0);
  129 +deque.toString(); //"-2,-1,0,1,2,3"
  130 +```
  131 +
  132 +**Aliases:** `insertFront`
  133 +
  134 +<hr>
  135 +
  136 +
  137 +#####`pop()` -> `dynamic`
  138 +
  139 +Pop off the item at the back of this queue.
  140 +
  141 +Note: The item will be removed from the queue. If you simply want to see what's at the back of the queue use [`peekBack()`](#peekback---dynamic) or [`.get(-1)`](#getint-index---dynamic).
  142 +
  143 +If the queue is empty, `undefined` is returned. If you need to differentiate between `undefined` values in the queue and `pop()` return value -
  144 +check the queue `.length` before popping.
  145 +
  146 +```js
  147 +var deque = new Deque([1,2,3]);
  148 +deque.pop(); //3
  149 +deque.pop(); //2
  150 +deque.pop(); //1
  151 +deque.pop(); //undefined
  152 +```
  153 +
  154 +**Aliases:** `removeBack`
  155 +
  156 +<hr>
  157 +
  158 +#####`shift()` -> `dynamic`
  159 +
  160 +Shifts off the item at the front of this queue.
  161 +
  162 +Note: The item will be removed from the queue. If you simply want to see what's at the front of the queue use [`peekFront()`](#peekfront---dynamic) or [`.get(0)`](#getint-index---dynamic).
  163 +
  164 +If the queue is empty, `undefined` is returned. If you need to differentiate between `undefined` values in the queue and `shift()` return value -
  165 +check the queue `.length` before shifting.
  166 +
  167 +```js
  168 +var deque = new Deque([1,2,3]);
  169 +deque.shift(); //1
  170 +deque.shift(); //2
  171 +deque.shift(); //3
  172 +deque.shift(); //undefined
  173 +```
  174 +
  175 +**Aliases:** `removeFront`, `dequeue`
  176 +
  177 +<hr>
  178 +
  179 +#####`toArray()` -> `Array`
  180 +
  181 +Returns the items in the queue as an array. Starting from the item in the front of the queue and ending to the item at the back of the queue.
  182 +
  183 +```js
  184 +var deque = new Deque([1,2,3]);
  185 +deque.push(4);
  186 +deque.unshift(0);
  187 +deque.toArray(); //[0,1,2,3,4]
  188 +```
  189 +
  190 +**Aliases:** `toJSON`
  191 +
  192 +<hr>
  193 +
  194 +#####`peekBack()` -> `dynamic`
  195 +
  196 +Returns the item that is at the back of this queue without removing it.
  197 +
  198 +If the queue is empty, `undefined` is returned.
  199 +
  200 +```js
  201 +var deque = new Deque([1,2,3]);
  202 +deque.push(4);
  203 +deque.peekBack(); //4
  204 +```
  205 +
  206 +<hr>
  207 +
  208 +#####`peekFront()` -> `dynamic`
  209 +
  210 +Returns the item that is at the front of this queue without removing it.
  211 +
  212 +If the queue is empty, `undefined` is returned.
  213 +
  214 +```js
  215 +var deque = new Deque([1,2,3]);
  216 +deque.push(4);
  217 +deque.peekFront(); //1
  218 +```
  219 +
  220 +<hr>
  221 +
  222 +#####`get(int index)` -> `dynamic`
  223 +
  224 +Returns the item that is at the given `index` of this queue without removing it.
  225 +
  226 +The index is zero-based, so `.get(0)` will return the item that is at the front, `.get(1)` will return
  227 +the item that comes after and so on.
  228 +
  229 +The index can be negative to read items at the back of the queue. `.get(-1)` returns the item that is at the back of the queue,
  230 +`.get(-2)` will return the item that comes before and so on.
  231 +
  232 +Returns `undefined` if `index` is not a valid index into the queue.
  233 +
  234 +```js
  235 +var deque = new Deque([1,2,3]);
  236 +deque.get(0); //1
  237 +deque.get(1); //2
  238 +deque.get(2); //3
  239 +
  240 +deque.get(-1); //3
  241 +deque.get(-2); //2
  242 +deque.get(-3); //1
  243 +```
  244 +
  245 +**Note**: Even though indexed accessor (e.g. `queue[0]`) could *appear* to return a correct value *sometimes*, this is completely unreliable. The numeric slots
  246 +of the deque object are internally used as an optimization and have no meaningful order or meaning to outside. Always use `.get()`.
  247 +
  248 +**Note**: The implementation has O(1) random access using `.get()`.
  249 +
  250 +<hr>
  251 +
  252 +#####`isEmpty()` -> `boolean`
  253 +
  254 +Return `true` if this queue is empty, `false` otherwise.
  255 +
  256 +```js
  257 +var deque = new Deque();
  258 +deque.isEmpty(); //true
  259 +deque.push(1);
  260 +deque.isEmpty(); //false
  261 +```
  262 +
  263 +<hr>
  264 +
  265 +#####`clear()` -> `void`
  266 +
  267 +Remove all items from this queue. Does not change the queue's capacity.
  268 +
  269 +```js
  270 +var deque = new Deque([1,2,3]);
  271 +deque.toString(); //"1,2,3"
  272 +deque.clear();
  273 +deque.toString(); //""
  274 +```
  275 +<hr>
  276 +
  277 +#Performance
  278 +
  279 +Clone the repo and `npm install`. Then run the `bench` script.
  280 +
  281 +##1000 items in the queue
  282 +
  283 + double-ended-queue x 15,532,714 ops/sec ±0.19% (96 runs sampled)
  284 + built-in array x 6,501,398 ops/sec ±0.87% (95 runs sampled)
  285 + node-deque x 2,938,068 ops/sec ±3.50% (68 runs sampled)
  286 +
  287 +##2 million items in the queue
  288 +
  289 + double-ended-queue x 14,425,547 ops/sec ±0.17% (94 runs sampled)
  290 + node-deque x 2,815,628 ops/sec ±10.56% (76 runs sampled)
  291 + built-in array x 19.23 ops/sec ±0.35% (51 runs sampled)
  292 +
  293 +Noteworthy is just how bad the degradation can be for built-in array when V8 cannot use the trick.
  1 +/**
  2 + * Copyright (c) 2013 Petka Antonov
  3 + *
  4 + * Permission is hereby granted, free of charge, to any person obtaining a copy
  5 + * of this software and associated documentation files (the "Software"), to deal
  6 + * in the Software without restriction, including without limitation the rights
  7 + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8 + * copies of the Software, and to permit persons to whom the Software is
  9 + * furnished to do so, subject to the following conditions:</p>
  10 + *
  11 + * The above copyright notice and this permission notice shall be included in
  12 + * all copies or substantial portions of the Software.
  13 + *
  14 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17 + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19 + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20 + * THE SOFTWARE.
  21 + */
  22 +"use strict";
  23 +function Deque(capacity) {
  24 + this._capacity = getCapacity(capacity);
  25 + this._length = 0;
  26 + this._front = 0;
  27 + if (isArray(capacity)) {
  28 + var len = capacity.length;
  29 + for (var i = 0; i < len; ++i) {
  30 + this[i] = capacity[i];
  31 + }
  32 + this._length = len;
  33 + }
  34 +}
  35 +
  36 +Deque.prototype.toArray = function Deque$toArray() {
  37 + var len = this._length;
  38 + var ret = new Array(len);
  39 + var front = this._front;
  40 + var capacity = this._capacity;
  41 + for (var j = 0; j < len; ++j) {
  42 + ret[j] = this[(front + j) & (capacity - 1)];
  43 + }
  44 + return ret;
  45 +};
  46 +
  47 +Deque.prototype.push = function Deque$push(item) {
  48 + var argsLength = arguments.length;
  49 + var length = this._length;
  50 + if (argsLength > 1) {
  51 + var capacity = this._capacity;
  52 + if (length + argsLength > capacity) {
  53 + for (var i = 0; i < argsLength; ++i) {
  54 + this._checkCapacity(length + 1);
  55 + var j = (this._front + length) & (this._capacity - 1);
  56 + this[j] = arguments[i];
  57 + length++;
  58 + this._length = length;
  59 + }
  60 + return length;
  61 + }
  62 + else {
  63 + var j = this._front;
  64 + for (var i = 0; i < argsLength; ++i) {
  65 + this[(j + length) & (capacity - 1)] = arguments[i];
  66 + j++;
  67 + }
  68 + this._length = length + argsLength;
  69 + return length + argsLength;
  70 + }
  71 +
  72 + }
  73 +
  74 + if (argsLength === 0) return length;
  75 +
  76 + this._checkCapacity(length + 1);
  77 + var i = (this._front + length) & (this._capacity - 1);
  78 + this[i] = item;
  79 + this._length = length + 1;
  80 + return length + 1;
  81 +};
  82 +
  83 +Deque.prototype.pop = function Deque$pop() {
  84 + var length = this._length;
  85 + if (length === 0) {
  86 + return void 0;
  87 + }
  88 + var i = (this._front + length - 1) & (this._capacity - 1);
  89 + var ret = this[i];
  90 + this[i] = void 0;
  91 + this._length = length - 1;
  92 + return ret;
  93 +};
  94 +
  95 +Deque.prototype.shift = function Deque$shift() {
  96 + var length = this._length;
  97 + if (length === 0) {
  98 + return void 0;
  99 + }
  100 + var front = this._front;
  101 + var ret = this[front];
  102 + this[front] = void 0;
  103 + this._front = (front + 1) & (this._capacity - 1);
  104 + this._length = length - 1;
  105 + return ret;
  106 +};
  107 +
  108 +Deque.prototype.unshift = function Deque$unshift(item) {
  109 + var length = this._length;
  110 + var argsLength = arguments.length;
  111 +
  112 +
  113 + if (argsLength > 1) {
  114 + var capacity = this._capacity;
  115 + if (length + argsLength > capacity) {
  116 + for (var i = argsLength - 1; i >= 0; i--) {
  117 + this._checkCapacity(length + 1);
  118 + var capacity = this._capacity;
  119 + var j = (((( this._front - 1 ) &
  120 + ( capacity - 1) ) ^ capacity ) - capacity );
  121 + this[j] = arguments[i];
  122 + length++;
  123 + this._length = length;
  124 + this._front = j;
  125 + }
  126 + return length;
  127 + }
  128 + else {
  129 + var front = this._front;
  130 + for (var i = argsLength - 1; i >= 0; i--) {
  131 + var j = (((( front - 1 ) &
  132 + ( capacity - 1) ) ^ capacity ) - capacity );
  133 + this[j] = arguments[i];
  134 + front = j;
  135 + }
  136 + this._front = front;
  137 + this._length = length + argsLength;
  138 + return length + argsLength;
  139 + }
  140 + }
  141 +
  142 + if (argsLength === 0) return length;
  143 +
  144 + this._checkCapacity(length + 1);
  145 + var capacity = this._capacity;
  146 + var i = (((( this._front - 1 ) &
  147 + ( capacity - 1) ) ^ capacity ) - capacity );
  148 + this[i] = item;
  149 + this._length = length + 1;
  150 + this._front = i;
  151 + return length + 1;
  152 +};
  153 +
  154 +Deque.prototype.peekBack = function Deque$peekBack() {
  155 + var length = this._length;
  156 + if (length === 0) {
  157 + return void 0;
  158 + }
  159 + var index = (this._front + length - 1) & (this._capacity - 1);
  160 + return this[index];
  161 +};
  162 +
  163 +Deque.prototype.peekFront = function Deque$peekFront() {
  164 + if (this._length === 0) {
  165 + return void 0;
  166 + }
  167 + return this[this._front];
  168 +};
  169 +
  170 +Deque.prototype.get = function Deque$get(index) {
  171 + var i = index;
  172 + if ((i !== (i | 0))) {
  173 + return void 0;
  174 + }
  175 + var len = this._length;
  176 + if (i < 0) {
  177 + i = i + len;
  178 + }
  179 + if (i < 0 || i >= len) {
  180 + return void 0;
  181 + }
  182 + return this[(this._front + i) & (this._capacity - 1)];
  183 +};
  184 +
  185 +Deque.prototype.isEmpty = function Deque$isEmpty() {
  186 + return this._length === 0;
  187 +};
  188 +
  189 +Deque.prototype.clear = function Deque$clear() {
  190 + var len = this._length;
  191 + var front = this._front;
  192 + var capacity = this._capacity;
  193 + for (var j = 0; j < len; ++j) {
  194 + this[(front + j) & (capacity - 1)] = void 0;
  195 + }
  196 + this._length = 0;
  197 + this._front = 0;
  198 +};
  199 +
  200 +Deque.prototype.toString = function Deque$toString() {
  201 + return this.toArray().toString();
  202 +};
  203 +
  204 +Deque.prototype.valueOf = Deque.prototype.toString;
  205 +Deque.prototype.removeFront = Deque.prototype.shift;
  206 +Deque.prototype.removeBack = Deque.prototype.pop;
  207 +Deque.prototype.insertFront = Deque.prototype.unshift;
  208 +Deque.prototype.insertBack = Deque.prototype.push;
  209 +Deque.prototype.enqueue = Deque.prototype.push;
  210 +Deque.prototype.dequeue = Deque.prototype.shift;
  211 +Deque.prototype.toJSON = Deque.prototype.toArray;
  212 +
  213 +Object.defineProperty(Deque.prototype, "length", {
  214 + get: function() {
  215 + return this._length;
  216 + },
  217 + set: function() {
  218 + throw new RangeError("");
  219 + }
  220 +});
  221 +
  222 +Deque.prototype._checkCapacity = function Deque$_checkCapacity(size) {
  223 + if (this._capacity < size) {
  224 + this._resizeTo(getCapacity(this._capacity * 1.5 + 16));
  225 + }
  226 +};
  227 +
  228 +Deque.prototype._resizeTo = function Deque$_resizeTo(capacity) {
  229 + var oldCapacity = this._capacity;
  230 + this._capacity = capacity;
  231 + var front = this._front;
  232 + var length = this._length;
  233 + if (front + length > oldCapacity) {
  234 + var moveItemsCount = (front + length) & (oldCapacity - 1);
  235 + arrayMove(this, 0, this, oldCapacity, moveItemsCount);
  236 + }
  237 +};
  238 +
  239 +
  240 +var isArray = Array.isArray;
  241 +
  242 +function arrayMove(src, srcIndex, dst, dstIndex, len) {
  243 + for (var j = 0; j < len; ++j) {
  244 + dst[j + dstIndex] = src[j + srcIndex];
  245 + src[j + srcIndex] = void 0;
  246 + }
  247 +}
  248 +
  249 +function pow2AtLeast(n) {
  250 + n = n >>> 0;
  251 + n = n - 1;
  252 + n = n | (n >> 1);
  253 + n = n | (n >> 2);
  254 + n = n | (n >> 4);
  255 + n = n | (n >> 8);
  256 + n = n | (n >> 16);
  257 + return n + 1;
  258 +}
  259 +
  260 +function getCapacity(capacity) {
  261 + if (typeof capacity !== "number") {
  262 + if (isArray(capacity)) {
  263 + capacity = capacity.length;
  264 + }
  265 + else {
  266 + return 16;
  267 + }
  268 + }
  269 + return pow2AtLeast(
  270 + Math.min(
  271 + Math.max(16, capacity), 1073741824)
  272 + );
  273 +}
  274 +
  275 +module.exports = Deque;
  1 +{
  2 + "_args": [
  3 + [
  4 + {
  5 + "raw": "double-ended-queue@^2.1.0-0",
  6 + "scope": null,
  7 + "escapedName": "double-ended-queue",
  8 + "name": "double-ended-queue",
  9 + "rawSpec": "^2.1.0-0",
  10 + "spec": ">=2.1.0-0 <3.0.0",
  11 + "type": "range"
  12 + },
  13 + "/Users/fzy/project/koa2_Sequelize_project/node_modules/redis"
  14 + ]
  15 + ],
  16 + "_from": "double-ended-queue@>=2.1.0-0 <3.0.0",
  17 + "_id": "double-ended-queue@2.1.0-0",
  18 + "_inCache": true,
  19 + "_location": "/double-ended-queue",
  20 + "_nodeVersion": "0.10.34",
  21 + "_npmUser": {
  22 + "name": "esailija",
  23 + "email": "petka_antonov@hotmail.com"
  24 + },
  25 + "_npmVersion": "2.1.12",
  26 + "_phantomChildren": {},
  27 + "_requested": {
  28 + "raw": "double-ended-queue@^2.1.0-0",
  29 + "scope": null,
  30 + "escapedName": "double-ended-queue",
  31 + "name": "double-ended-queue",
  32 + "rawSpec": "^2.1.0-0",
  33 + "spec": ">=2.1.0-0 <3.0.0",
  34 + "type": "range"
  35 + },
  36 + "_requiredBy": [
  37 + "/redis"
  38 + ],
  39 + "_resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
  40 + "_shasum": "103d3527fd31528f40188130c841efdd78264e5c",
  41 + "_shrinkwrap": null,
  42 + "_spec": "double-ended-queue@^2.1.0-0",
  43 + "_where": "/Users/fzy/project/koa2_Sequelize_project/node_modules/redis",
  44 + "author": {
  45 + "name": "Petka Antonov",
  46 + "email": "petka_antonov@hotmail.com",
  47 + "url": "http://github.com/petkaantonov/"
  48 + },
  49 + "bugs": {
  50 + "url": "http://github.com/petkaantonov/deque/issues"
  51 + },
  52 + "dependencies": {},
  53 + "description": "Extremely fast double-ended queue implementation",
  54 + "devDependencies": {
  55 + "acorn": "~0.3.1",
  56 + "benchmark": "~1.0.0",
  57 + "bluebird": "~0.11",
  58 + "deque": "0.0.4",
  59 + "grunt": "~0.4.1",
  60 + "grunt-cli": "~0.1.9",
  61 + "grunt-contrib-jshint": "~0.6.4",
  62 + "jshint-stylish": "latest",
  63 + "mocha": "~1.12.1",
  64 + "q": "~0.9.7",
  65 + "semver-utils": "~1.1.0"
  66 + },
  67 + "directories": {},
  68 + "dist": {
  69 + "shasum": "103d3527fd31528f40188130c841efdd78264e5c",
  70 + "tarball": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz"
  71 + },
  72 + "gitHead": "51eada75cea686f1eb0c8bb5be486ac630e9b7ee",
  73 + "homepage": "https://github.com/petkaantonov/deque",
  74 + "keywords": [
  75 + "data-structure",
  76 + "data-structures",
  77 + "queue",
  78 + "deque",
  79 + "double-ended-queue"
  80 + ],
  81 + "license": "MIT",
  82 + "main": "./js/deque.js",
  83 + "maintainers": [
  84 + {
  85 + "name": "esailija",
  86 + "email": "petka_antonov@hotmail.com"
  87 + }
  88 + ],
  89 + "name": "double-ended-queue",
  90 + "optionalDependencies": {},
  91 + "readme": "#Introduction\n\nExtremely fast [double-ended queue](http://en.wikipedia.org/wiki/Double-ended_queue) implementation. Double-ended queue can also be used as a:\n\n- [Stack](http://en.wikipedia.org/wiki/Stack_\\(abstract_data_type\\))\n- [Queue](http://en.wikipedia.org/wiki/Queue_\\(data_structure\\))\n\nThe implementation is GC and CPU cache friendly [circular buffer](http://en.wikipedia.org/wiki/Circular_buffer). [It will run circles around any \"linked list\" implementation](#performance).\n\nEvery queue operation is done in constant `O(1)` - including random access from `.get()`.\n\n#Topics\n\n- [Quick start](#quick-start)\n- [Why not use an Array?](#why-not-use-an-array)\n- [Using double-ended queue as a normal queue](#using-double-ended-queue-as-a-normal-queue)\n- [API reference and examples](#api)\n- [Performance](#performance)\n\n#Quick start\n\n npm install double-ended-queue\n\n```js\nvar Deque = require(\"double-ended-queue\");\n\nvar deque = new Deque([1,2,3,4]);\ndeque.shift(); //1\ndeque.pop(); //4\n```\n\n#Why not use an Array?\n\nArrays take linear `O(N)` time to do `shift` and `unshift` operations. That means in theory that an array with 1000 items is 1000x slower to do those operations than a deque with 1000 items. 10000x slower with 10000 items and so on.\n\nV8 implements [a trick for small arrays](https://code.google.com/p/v8/issues/detail?id=3059) where these operations are done in constant time, however even with this trick deque is still 4x faster.\n\nBut arrays use \"native\" methods, they must be faster!\n\nIn V8, there is almost no advantage for a method to be a built-in. In fact many times built-ins are at a severe disadvantage of having to implement far more complex semantics than is actually needed in practice. For example, sparse array handling punishes almost every built-in array method even though nobody uses sparse arrays as is evidenced by the popularity of the underscore library which doesn't handle sparse arrays in the same way across different browsers.\n\n#Using double-ended queue as a normal queue\n\nQueue is a more commonly needed data structure however a separate implementation does not provide any advantage in terms of performance. Aliases are provided specifically for the queue use-case. You may use `.enqueue(items...)` to enqueue item(s) and `.dequeue()` to dequeue an item.\n\n#API\n\n- [`new Deque()`](#new-deque---deque)\n- [`new Deque(Array items)`](#new-dequearray-items---deque)\n- [`new Deque(int capacity)`](#new-dequeint-capacity---deque)\n- [`push(dynamic items...)`](#pushdynamic-items---int)\n- [`unshift(dynamic items...)`](#unshiftdynamic-items---int)\n- [`pop()`](#pop---dynamic)\n- [`shift()`](#shift---dynamic)\n- [`toArray()`](#toarray---array)\n- [`peekBack()`](#peekback---dynamic)\n- [`peekFront()`](#peekfront---dynamic)\n- [`get(int index)`](#getint-index---dynamic)\n- [`isEmpty()`](#isempty---boolean)\n- [`clear()`](#clear---void)\n\n#####`new Deque()` -> `Deque`\n\nCreates an empty double-ended queue with initial capacity of 16. If you know the optimal size before-hand, use [`new Deque(int capacity)`](#new-dequeint-capacity---deque).\n\n```js\nvar deque = new Deque();\ndeque.push(1, 2, 3);\ndeque.shift(); //1\ndeque.pop(); //3\n```\n\n<hr>\n\n#####`new Deque(Array items)` -> `Deque`\n\nCreates a double-ended queue from `items`.\n\n```js\nvar deque = new Deque([1,2,3,4]);\ndeque.shift(); //1\ndeque.pop(); //4\n```\n\n<hr>\n\n#####`new Deque(int capacity)` -> `Deque`\n\nCreates an empty double-ended queue with the given `capacity`. `Capacity` should be the maximum amount of items the queue will hold at a given time.\n\nThe reason to give an initial capacity is to avoid potentially expensive resizing operations at runtime.\n\n```js\nvar deque = new Deque(100);\ndeque.push(1, 2, 3);\ndeque.shift(); //1\ndeque.pop(); //3\n```\n\n<hr>\n\n#####`push(dynamic items...)` -> `int`\n\nPush items to the back of this queue. Returns the amount of items currently in the queue after the operation.\n\n```js\nvar deque = new Deque();\ndeque.push(1);\ndeque.pop(); //1\ndeque.push(1, 2, 3);\ndeque.shift(); //1\ndeque.shift(); //2\ndeque.shift(); //3\n```\n\n**Aliases:** `enqueue`, `insertBack`\n\n<hr>\n\n#####`unshift(dynamic items...)` -> `int`\n\nUnshift items to the front of this queue. Returns the amount of items currently in the queue after the operation.\n\n```js\nvar deque = new Deque([2,3]);\ndeque.unshift(1);\ndeque.toString(); //\"1,2,3\"\ndeque.unshift(-2, -1, 0);\ndeque.toString(); //\"-2,-1,0,1,2,3\"\n```\n\n**Aliases:** `insertFront`\n\n<hr>\n\n\n#####`pop()` -> `dynamic`\n\nPop off the item at the back of this queue.\n\nNote: The item will be removed from the queue. If you simply want to see what's at the back of the queue use [`peekBack()`](#peekback---dynamic) or [`.get(-1)`](#getint-index---dynamic).\n\nIf the queue is empty, `undefined` is returned. If you need to differentiate between `undefined` values in the queue and `pop()` return value -\ncheck the queue `.length` before popping.\n\n```js\nvar deque = new Deque([1,2,3]);\ndeque.pop(); //3\ndeque.pop(); //2\ndeque.pop(); //1\ndeque.pop(); //undefined\n```\n\n**Aliases:** `removeBack`\n\n<hr>\n\n#####`shift()` -> `dynamic`\n\nShifts off the item at the front of this queue.\n\nNote: The item will be removed from the queue. If you simply want to see what's at the front of the queue use [`peekFront()`](#peekfront---dynamic) or [`.get(0)`](#getint-index---dynamic).\n\nIf the queue is empty, `undefined` is returned. If you need to differentiate between `undefined` values in the queue and `shift()` return value -\ncheck the queue `.length` before shifting.\n\n```js\nvar deque = new Deque([1,2,3]);\ndeque.shift(); //1\ndeque.shift(); //2\ndeque.shift(); //3\ndeque.shift(); //undefined\n```\n\n**Aliases:** `removeFront`, `dequeue`\n\n<hr>\n\n#####`toArray()` -> `Array`\n\nReturns the items in the queue as an array. Starting from the item in the front of the queue and ending to the item at the back of the queue.\n\n```js\nvar deque = new Deque([1,2,3]);\ndeque.push(4);\ndeque.unshift(0);\ndeque.toArray(); //[0,1,2,3,4]\n```\n\n**Aliases:** `toJSON`\n\n<hr>\n\n#####`peekBack()` -> `dynamic`\n\nReturns the item that is at the back of this queue without removing it.\n\nIf the queue is empty, `undefined` is returned.\n\n```js\nvar deque = new Deque([1,2,3]);\ndeque.push(4);\ndeque.peekBack(); //4\n```\n\n<hr>\n\n#####`peekFront()` -> `dynamic`\n\nReturns the item that is at the front of this queue without removing it.\n\nIf the queue is empty, `undefined` is returned.\n\n```js\nvar deque = new Deque([1,2,3]);\ndeque.push(4);\ndeque.peekFront(); //1\n```\n\n<hr>\n\n#####`get(int index)` -> `dynamic`\n\nReturns the item that is at the given `index` of this queue without removing it.\n\nThe index is zero-based, so `.get(0)` will return the item that is at the front, `.get(1)` will return\nthe item that comes after and so on.\n\nThe index can be negative to read items at the back of the queue. `.get(-1)` returns the item that is at the back of the queue,\n`.get(-2)` will return the item that comes before and so on.\n\nReturns `undefined` if `index` is not a valid index into the queue.\n\n```js\nvar deque = new Deque([1,2,3]);\ndeque.get(0); //1\ndeque.get(1); //2\ndeque.get(2); //3\n\ndeque.get(-1); //3\ndeque.get(-2); //2\ndeque.get(-3); //1\n```\n\n**Note**: Even though indexed accessor (e.g. `queue[0]`) could *appear* to return a correct value *sometimes*, this is completely unreliable. The numeric slots\nof the deque object are internally used as an optimization and have no meaningful order or meaning to outside. Always use `.get()`.\n\n**Note**: The implementation has O(1) random access using `.get()`.\n\n<hr>\n\n#####`isEmpty()` -> `boolean`\n\nReturn `true` if this queue is empty, `false` otherwise.\n\n```js\nvar deque = new Deque();\ndeque.isEmpty(); //true\ndeque.push(1);\ndeque.isEmpty(); //false\n```\n\n<hr>\n\n#####`clear()` -> `void`\n\nRemove all items from this queue. Does not change the queue's capacity.\n\n```js\nvar deque = new Deque([1,2,3]);\ndeque.toString(); //\"1,2,3\"\ndeque.clear();\ndeque.toString(); //\"\"\n```\n<hr>\n\n#Performance\n\nClone the repo and `npm install`. Then run the `bench` script.\n\n##1000 items in the queue\n\n double-ended-queue x 15,532,714 ops/sec ±0.19% (96 runs sampled)\n built-in array x 6,501,398 ops/sec ±0.87% (95 runs sampled)\n node-deque x 2,938,068 ops/sec ±3.50% (68 runs sampled)\n\n##2 million items in the queue\n\n double-ended-queue x 14,425,547 ops/sec ±0.17% (94 runs sampled)\n node-deque x 2,815,628 ops/sec ±10.56% (76 runs sampled)\n built-in array x 19.23 ops/sec ±0.35% (51 runs sampled)\n\nNoteworthy is just how bad the degradation can be for built-in array when V8 cannot use the trick.\n",
  92 + "readmeFilename": "README.md",
  93 + "repository": {
  94 + "type": "git",
  95 + "url": "git://github.com/petkaantonov/deque.git"
  96 + },
  97 + "scripts": {
  98 + "test": "grunt test"
  99 + },
  100 + "version": "2.1.0-0"
  101 +}
  1 +# Logs
  2 +logs
  3 +*.log
  4 +
  5 +# Runtime data
  6 +pids
  7 +*.pid
  8 +*.seed
  9 +*.rdb
  10 +
  11 +# Directory for instrumented libs generated by jscoverage/JSCover
  12 +lib-cov
  13 +
  14 +# Coverage directory used by tools like istanbul
  15 +coverage
  16 +
  17 +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
  18 +.grunt
  19 +
  20 +# Compiled binary addons (http://nodejs.org/api/addons.html)
  21 +build/Release
  22 +
  23 +# Dependency directory
  24 +# Commenting this out is preferred by some people, see
  25 +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
  26 +node_modules
  27 +
  28 +# Users Environment Variables
  29 +.lock-wscript
  1 +language: node_js
  2 +sudo: false
  3 +node_js:
  4 + - "0.10"
  5 + - "0.12"
  6 + - "4"
  7 + - "5"
  8 +after_success:
  9 + - CODECLIMATE_REPO_TOKEN=b57723fafcf0516f275d6b380cd506fd082ea88d86507eb82c8abd489b9b9a09 node ./node_modules/.bin/codeclimate-test-reporter < coverage/lcov.info
  1 +The MIT License (MIT)
  2 +
  3 +Copyright (c) 2015 NodeRedis
  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 +
  1 +# Redis Commands
  2 +
  3 +[![Build Status](https://travis-ci.org/NodeRedis/redis-commands.png?branch=master)](https://travis-ci.org/NodeRedis/redis-commands)
  4 +[![Code Climate](https://codeclimate.com/github/NodeRedis/redis-commands/badges/gpa.svg)](https://codeclimate.com/github/NodeRedis/redis-commands)
  5 +[![Test Coverage](https://codeclimate.com/github/NodeRedis/redis-commands/badges/coverage.svg)](https://codeclimate.com/github/NodeRedis/redis-commands/coverage)
  6 +
  7 +This module exports all the commands that Redis supports.
  8 +
  9 +## Install
  10 +
  11 +```shell
  12 +$ npm install redis-commands
  13 +```
  14 +
  15 +## Usage
  16 +
  17 +```javascript
  18 +var commands = require('redis-commands');
  19 +```
  20 +
  21 +`.list` is an array contains all the lowercased commands:
  22 +
  23 +```javascript
  24 +commands.list.forEach(function (command) {
  25 + console.log(command);
  26 +});
  27 +```
  28 +
  29 +`.exists()` is used to check if the command exists:
  30 +
  31 +```javascript
  32 +commands.exists('set') // true
  33 +commands.exists('other-command') // false
  34 +```
  35 +
  36 +`.hasFlag()` is used to check if the command has the flag:
  37 +
  38 +```javascript
  39 +commands.hasFlag('set', 'readonly') // false
  40 +```
  41 +
  42 +`.getKeyIndexes()` is used to get the indexes of keys in the command arguments:
  43 +
  44 +```javascript
  45 +commands.getKeyIndexes('set', ['key', 'value']) // [0]
  46 +commands.getKeyIndexes('mget', ['key1', 'key2']) // [0, 1]
  47 +```
  48 +
  49 +## Acknowledgment
  50 +
  51 +Thank [@Yuan Chuan](https://github.com/yuanchuan) for the package name. The original redis-commands is renamed to [@yuanchuan/redis-commands](https://www.npmjs.com/package/@yuanchuan/redis-commands).
  1 +## v.1.3.1 - 25 Jan, 2017
  2 +
  3 +Bugfix
  4 +
  5 +- Fix require for for webpack
  6 +
  7 +## v.1.3.0 - 20 Oct, 2016
  8 +
  9 +Features
  10 +
  11 +- Rebuild the commands with the newest Redis unstable release
  12 +
  13 +## v.1.2.0 - 21 Apr, 2016
  14 +
  15 +Features
  16 +
  17 +- Added support for `MIGRATE [...] KEYS key1, key2` (Redis >= v.3.0.6)
  18 +- Added build sanity check for unhandled commands with moveable keys
  19 +- Rebuild the commands with the newest unstable release
  20 +- Improved performance of .getKeyIndexes()
  21 +
  22 +Bugfix
  23 +
  24 +- Fixed command command returning the wrong arity due to a Redis bug
  25 +- Fixed brpop command returning the wrong keystop due to a Redis bug
  26 +
  27 +## v.1.1.0 - 09 Feb, 2016
  28 +
  29 +Features
  30 +
  31 +- Added .exists() to check for command existence
  32 +- Improved performance of .hasFlag()
  1 +{
  2 + "append": {
  3 + "arity": 3,
  4 + "flags": [
  5 + "write",
  6 + "denyoom"
  7 + ],
  8 + "keyStart": 1,
  9 + "keyStop": 1,
  10 + "step": 1
  11 + },
  12 + "asking": {
  13 + "arity": 1,
  14 + "flags": [
  15 + "fast"
  16 + ],
  17 + "keyStart": 0,
  18 + "keyStop": 0,
  19 + "step": 0
  20 + },
  21 + "auth": {
  22 + "arity": 2,
  23 + "flags": [
  24 + "noscript",
  25 + "loading",
  26 + "stale",
  27 + "fast"
  28 + ],
  29 + "keyStart": 0,
  30 + "keyStop": 0,
  31 + "step": 0
  32 + },
  33 + "bgrewriteaof": {
  34 + "arity": 1,
  35 + "flags": [
  36 + "admin"
  37 + ],
  38 + "keyStart": 0,
  39 + "keyStop": 0,
  40 + "step": 0
  41 + },
  42 + "bgsave": {
  43 + "arity": -1,
  44 + "flags": [
  45 + "admin"
  46 + ],
  47 + "keyStart": 0,
  48 + "keyStop": 0,
  49 + "step": 0
  50 + },
  51 + "bitcount": {
  52 + "arity": -2,
  53 + "flags": [
  54 + "readonly"
  55 + ],
  56 + "keyStart": 1,
  57 + "keyStop": 1,
  58 + "step": 1
  59 + },
  60 + "bitfield": {
  61 + "arity": -2,
  62 + "flags": [
  63 + "write",
  64 + "denyoom"
  65 + ],
  66 + "keyStart": 1,
  67 + "keyStop": 1,
  68 + "step": 1
  69 + },
  70 + "bitop": {
  71 + "arity": -4,
  72 + "flags": [
  73 + "write",
  74 + "denyoom"
  75 + ],
  76 + "keyStart": 2,
  77 + "keyStop": -1,
  78 + "step": 1
  79 + },
  80 + "bitpos": {
  81 + "arity": -3,
  82 + "flags": [
  83 + "readonly"
  84 + ],
  85 + "keyStart": 1,
  86 + "keyStop": 1,
  87 + "step": 1
  88 + },
  89 + "blpop": {
  90 + "arity": -3,
  91 + "flags": [
  92 + "write",
  93 + "noscript"
  94 + ],
  95 + "keyStart": 1,
  96 + "keyStop": -2,
  97 + "step": 1
  98 + },
  99 + "brpop": {
  100 + "arity": -3,
  101 + "flags": [
  102 + "write",
  103 + "noscript"
  104 + ],
  105 + "keyStart": 1,
  106 + "keyStop": -2,
  107 + "step": 1
  108 + },
  109 + "brpoplpush": {
  110 + "arity": 4,
  111 + "flags": [
  112 + "write",
  113 + "denyoom",
  114 + "noscript"
  115 + ],
  116 + "keyStart": 1,
  117 + "keyStop": 2,
  118 + "step": 1
  119 + },
  120 + "client": {
  121 + "arity": -2,
  122 + "flags": [
  123 + "admin",
  124 + "noscript"
  125 + ],
  126 + "keyStart": 0,
  127 + "keyStop": 0,
  128 + "step": 0
  129 + },
  130 + "cluster": {
  131 + "arity": -2,
  132 + "flags": [
  133 + "admin"
  134 + ],
  135 + "keyStart": 0,
  136 + "keyStop": 0,
  137 + "step": 0
  138 + },
  139 + "command": {
  140 + "arity": 1,
  141 + "flags": [
  142 + "loading",
  143 + "stale"
  144 + ],
  145 + "keyStart": 0,
  146 + "keyStop": 0,
  147 + "step": 0
  148 + },
  149 + "config": {
  150 + "arity": -2,
  151 + "flags": [
  152 + "admin",
  153 + "loading",
  154 + "stale"
  155 + ],
  156 + "keyStart": 0,
  157 + "keyStop": 0,
  158 + "step": 0
  159 + },
  160 + "dbsize": {
  161 + "arity": 1,
  162 + "flags": [
  163 + "readonly",
  164 + "fast"
  165 + ],
  166 + "keyStart": 0,
  167 + "keyStop": 0,
  168 + "step": 0
  169 + },
  170 + "debug": {
  171 + "arity": -1,
  172 + "flags": [
  173 + "admin",
  174 + "noscript"
  175 + ],
  176 + "keyStart": 0,
  177 + "keyStop": 0,
  178 + "step": 0
  179 + },
  180 + "decr": {
  181 + "arity": 2,
  182 + "flags": [
  183 + "write",
  184 + "denyoom",
  185 + "fast"
  186 + ],
  187 + "keyStart": 1,
  188 + "keyStop": 1,
  189 + "step": 1
  190 + },
  191 + "decrby": {
  192 + "arity": 3,
  193 + "flags": [
  194 + "write",
  195 + "denyoom",
  196 + "fast"
  197 + ],
  198 + "keyStart": 1,
  199 + "keyStop": 1,
  200 + "step": 1
  201 + },
  202 + "del": {
  203 + "arity": -2,
  204 + "flags": [
  205 + "write"
  206 + ],
  207 + "keyStart": 1,
  208 + "keyStop": -1,
  209 + "step": 1
  210 + },
  211 + "discard": {
  212 + "arity": 1,
  213 + "flags": [
  214 + "noscript",
  215 + "fast"
  216 + ],
  217 + "keyStart": 0,
  218 + "keyStop": 0,
  219 + "step": 0
  220 + },
  221 + "dump": {
  222 + "arity": 2,
  223 + "flags": [
  224 + "readonly"
  225 + ],
  226 + "keyStart": 1,
  227 + "keyStop": 1,
  228 + "step": 1
  229 + },
  230 + "echo": {
  231 + "arity": 2,
  232 + "flags": [
  233 + "fast"
  234 + ],
  235 + "keyStart": 0,
  236 + "keyStop": 0,
  237 + "step": 0
  238 + },
  239 + "eval": {
  240 + "arity": -3,
  241 + "flags": [
  242 + "noscript",
  243 + "movablekeys"
  244 + ],
  245 + "keyStart": 0,
  246 + "keyStop": 0,
  247 + "step": 0
  248 + },
  249 + "evalsha": {
  250 + "arity": -3,
  251 + "flags": [
  252 + "noscript",
  253 + "movablekeys"
  254 + ],
  255 + "keyStart": 0,
  256 + "keyStop": 0,
  257 + "step": 0
  258 + },
  259 + "exec": {
  260 + "arity": 1,
  261 + "flags": [
  262 + "noscript",
  263 + "skip_monitor"
  264 + ],
  265 + "keyStart": 0,
  266 + "keyStop": 0,
  267 + "step": 0
  268 + },
  269 + "exists": {
  270 + "arity": -2,
  271 + "flags": [
  272 + "readonly",
  273 + "fast"
  274 + ],
  275 + "keyStart": 1,
  276 + "keyStop": -1,
  277 + "step": 1
  278 + },
  279 + "expire": {
  280 + "arity": 3,
  281 + "flags": [
  282 + "write",
  283 + "fast"
  284 + ],
  285 + "keyStart": 1,
  286 + "keyStop": 1,
  287 + "step": 1
  288 + },
  289 + "expireat": {
  290 + "arity": 3,
  291 + "flags": [
  292 + "write",
  293 + "fast"
  294 + ],
  295 + "keyStart": 1,
  296 + "keyStop": 1,
  297 + "step": 1
  298 + },
  299 + "flushall": {
  300 + "arity": -1,
  301 + "flags": [
  302 + "write"
  303 + ],
  304 + "keyStart": 0,
  305 + "keyStop": 0,
  306 + "step": 0
  307 + },
  308 + "flushdb": {
  309 + "arity": -1,
  310 + "flags": [
  311 + "write"
  312 + ],
  313 + "keyStart": 0,
  314 + "keyStop": 0,
  315 + "step": 0
  316 + },
  317 + "geoadd": {
  318 + "arity": -5,
  319 + "flags": [
  320 + "write",
  321 + "denyoom"
  322 + ],
  323 + "keyStart": 1,
  324 + "keyStop": 1,
  325 + "step": 1
  326 + },
  327 + "geodist": {
  328 + "arity": -4,
  329 + "flags": [
  330 + "readonly"
  331 + ],
  332 + "keyStart": 1,
  333 + "keyStop": 1,
  334 + "step": 1
  335 + },
  336 + "geohash": {
  337 + "arity": -2,
  338 + "flags": [
  339 + "readonly"
  340 + ],
  341 + "keyStart": 1,
  342 + "keyStop": 1,
  343 + "step": 1
  344 + },
  345 + "geopos": {
  346 + "arity": -2,
  347 + "flags": [
  348 + "readonly"
  349 + ],
  350 + "keyStart": 1,
  351 + "keyStop": 1,
  352 + "step": 1
  353 + },
  354 + "georadius": {
  355 + "arity": -6,
  356 + "flags": [
  357 + "write"
  358 + ],
  359 + "keyStart": 1,
  360 + "keyStop": 1,
  361 + "step": 1
  362 + },
  363 + "georadiusbymember": {
  364 + "arity": -5,
  365 + "flags": [
  366 + "write"
  367 + ],
  368 + "keyStart": 1,
  369 + "keyStop": 1,
  370 + "step": 1
  371 + },
  372 + "get": {
  373 + "arity": 2,
  374 + "flags": [
  375 + "readonly",
  376 + "fast"
  377 + ],
  378 + "keyStart": 1,
  379 + "keyStop": 1,
  380 + "step": 1
  381 + },
  382 + "getbit": {
  383 + "arity": 3,
  384 + "flags": [
  385 + "readonly",
  386 + "fast"
  387 + ],
  388 + "keyStart": 1,
  389 + "keyStop": 1,
  390 + "step": 1
  391 + },
  392 + "getrange": {
  393 + "arity": 4,
  394 + "flags": [
  395 + "readonly"
  396 + ],
  397 + "keyStart": 1,
  398 + "keyStop": 1,
  399 + "step": 1
  400 + },
  401 + "getset": {
  402 + "arity": 3,
  403 + "flags": [
  404 + "write",
  405 + "denyoom"
  406 + ],
  407 + "keyStart": 1,
  408 + "keyStop": 1,
  409 + "step": 1
  410 + },
  411 + "hdel": {
  412 + "arity": -3,
  413 + "flags": [
  414 + "write",
  415 + "fast"
  416 + ],
  417 + "keyStart": 1,
  418 + "keyStop": 1,
  419 + "step": 1
  420 + },
  421 + "hexists": {
  422 + "arity": 3,
  423 + "flags": [
  424 + "readonly",
  425 + "fast"
  426 + ],
  427 + "keyStart": 1,
  428 + "keyStop": 1,
  429 + "step": 1
  430 + },
  431 + "hget": {
  432 + "arity": 3,
  433 + "flags": [
  434 + "readonly",
  435 + "fast"
  436 + ],
  437 + "keyStart": 1,
  438 + "keyStop": 1,
  439 + "step": 1
  440 + },
  441 + "hgetall": {
  442 + "arity": 2,
  443 + "flags": [
  444 + "readonly"
  445 + ],
  446 + "keyStart": 1,
  447 + "keyStop": 1,
  448 + "step": 1
  449 + },
  450 + "hincrby": {
  451 + "arity": 4,
  452 + "flags": [
  453 + "write",
  454 + "denyoom",
  455 + "fast"
  456 + ],
  457 + "keyStart": 1,
  458 + "keyStop": 1,
  459 + "step": 1
  460 + },
  461 + "hincrbyfloat": {
  462 + "arity": 4,
  463 + "flags": [
  464 + "write",
  465 + "denyoom",
  466 + "fast"
  467 + ],
  468 + "keyStart": 1,
  469 + "keyStop": 1,
  470 + "step": 1
  471 + },
  472 + "hkeys": {
  473 + "arity": 2,
  474 + "flags": [
  475 + "readonly",
  476 + "sort_for_script"
  477 + ],
  478 + "keyStart": 1,
  479 + "keyStop": 1,
  480 + "step": 1
  481 + },
  482 + "hlen": {
  483 + "arity": 2,
  484 + "flags": [
  485 + "readonly",
  486 + "fast"
  487 + ],
  488 + "keyStart": 1,
  489 + "keyStop": 1,
  490 + "step": 1
  491 + },
  492 + "hmget": {
  493 + "arity": -3,
  494 + "flags": [
  495 + "readonly"
  496 + ],
  497 + "keyStart": 1,
  498 + "keyStop": 1,
  499 + "step": 1
  500 + },
  501 + "hmset": {
  502 + "arity": -4,
  503 + "flags": [
  504 + "write",
  505 + "denyoom"
  506 + ],
  507 + "keyStart": 1,
  508 + "keyStop": 1,
  509 + "step": 1
  510 + },
  511 + "host:": {
  512 + "arity": -1,
  513 + "flags": [
  514 + "loading",
  515 + "stale"
  516 + ],
  517 + "keyStart": 0,
  518 + "keyStop": 0,
  519 + "step": 0
  520 + },
  521 + "hscan": {
  522 + "arity": -3,
  523 + "flags": [
  524 + "readonly",
  525 + "random"
  526 + ],
  527 + "keyStart": 1,
  528 + "keyStop": 1,
  529 + "step": 1
  530 + },
  531 + "hset": {
  532 + "arity": 4,
  533 + "flags": [
  534 + "write",
  535 + "denyoom",
  536 + "fast"
  537 + ],
  538 + "keyStart": 1,
  539 + "keyStop": 1,
  540 + "step": 1
  541 + },
  542 + "hsetnx": {
  543 + "arity": 4,
  544 + "flags": [
  545 + "write",
  546 + "denyoom",
  547 + "fast"
  548 + ],
  549 + "keyStart": 1,
  550 + "keyStop": 1,
  551 + "step": 1
  552 + },
  553 + "hstrlen": {
  554 + "arity": 3,
  555 + "flags": [
  556 + "readonly",
  557 + "fast"
  558 + ],
  559 + "keyStart": 1,
  560 + "keyStop": 1,
  561 + "step": 1
  562 + },
  563 + "hvals": {
  564 + "arity": 2,
  565 + "flags": [
  566 + "readonly",
  567 + "sort_for_script"
  568 + ],
  569 + "keyStart": 1,
  570 + "keyStop": 1,
  571 + "step": 1
  572 + },
  573 + "incr": {
  574 + "arity": 2,
  575 + "flags": [
  576 + "write",
  577 + "denyoom",
  578 + "fast"
  579 + ],
  580 + "keyStart": 1,
  581 + "keyStop": 1,
  582 + "step": 1
  583 + },
  584 + "incrby": {
  585 + "arity": 3,
  586 + "flags": [
  587 + "write",
  588 + "denyoom",
  589 + "fast"
  590 + ],
  591 + "keyStart": 1,
  592 + "keyStop": 1,
  593 + "step": 1
  594 + },
  595 + "incrbyfloat": {
  596 + "arity": 3,
  597 + "flags": [
  598 + "write",
  599 + "denyoom",
  600 + "fast"
  601 + ],
  602 + "keyStart": 1,
  603 + "keyStop": 1,
  604 + "step": 1
  605 + },
  606 + "info": {
  607 + "arity": -1,
  608 + "flags": [
  609 + "loading",
  610 + "stale"
  611 + ],
  612 + "keyStart": 0,
  613 + "keyStop": 0,
  614 + "step": 0
  615 + },
  616 + "keys": {
  617 + "arity": 2,
  618 + "flags": [
  619 + "readonly",
  620 + "sort_for_script"
  621 + ],
  622 + "keyStart": 0,
  623 + "keyStop": 0,
  624 + "step": 0
  625 + },
  626 + "lastsave": {
  627 + "arity": 1,
  628 + "flags": [
  629 + "random",
  630 + "fast"
  631 + ],
  632 + "keyStart": 0,
  633 + "keyStop": 0,
  634 + "step": 0
  635 + },
  636 + "latency": {
  637 + "arity": -2,
  638 + "flags": [
  639 + "admin",
  640 + "noscript",
  641 + "loading",
  642 + "stale"
  643 + ],
  644 + "keyStart": 0,
  645 + "keyStop": 0,
  646 + "step": 0
  647 + },
  648 + "lindex": {
  649 + "arity": 3,
  650 + "flags": [
  651 + "readonly"
  652 + ],
  653 + "keyStart": 1,
  654 + "keyStop": 1,
  655 + "step": 1
  656 + },
  657 + "linsert": {
  658 + "arity": 5,
  659 + "flags": [
  660 + "write",
  661 + "denyoom"
  662 + ],
  663 + "keyStart": 1,
  664 + "keyStop": 1,
  665 + "step": 1
  666 + },
  667 + "llen": {
  668 + "arity": 2,
  669 + "flags": [
  670 + "readonly",
  671 + "fast"
  672 + ],
  673 + "keyStart": 1,
  674 + "keyStop": 1,
  675 + "step": 1
  676 + },
  677 + "lpop": {
  678 + "arity": 2,
  679 + "flags": [
  680 + "write",
  681 + "fast"
  682 + ],
  683 + "keyStart": 1,
  684 + "keyStop": 1,
  685 + "step": 1
  686 + },
  687 + "lpush": {
  688 + "arity": -3,
  689 + "flags": [
  690 + "write",
  691 + "denyoom",
  692 + "fast"
  693 + ],
  694 + "keyStart": 1,
  695 + "keyStop": 1,
  696 + "step": 1
  697 + },
  698 + "lpushx": {
  699 + "arity": -3,
  700 + "flags": [
  701 + "write",
  702 + "denyoom",
  703 + "fast"
  704 + ],
  705 + "keyStart": 1,
  706 + "keyStop": 1,
  707 + "step": 1
  708 + },
  709 + "lrange": {
  710 + "arity": 4,
  711 + "flags": [
  712 + "readonly"
  713 + ],
  714 + "keyStart": 1,
  715 + "keyStop": 1,
  716 + "step": 1
  717 + },
  718 + "lrem": {
  719 + "arity": 4,
  720 + "flags": [
  721 + "write"
  722 + ],
  723 + "keyStart": 1,
  724 + "keyStop": 1,
  725 + "step": 1
  726 + },
  727 + "lset": {
  728 + "arity": 4,
  729 + "flags": [
  730 + "write",
  731 + "denyoom"
  732 + ],
  733 + "keyStart": 1,
  734 + "keyStop": 1,
  735 + "step": 1
  736 + },
  737 + "ltrim": {
  738 + "arity": 4,
  739 + "flags": [
  740 + "write"
  741 + ],
  742 + "keyStart": 1,
  743 + "keyStop": 1,
  744 + "step": 1
  745 + },
  746 + "memory": {
  747 + "arity": -2,
  748 + "flags": [
  749 + "readonly"
  750 + ],
  751 + "keyStart": 0,
  752 + "keyStop": 0,
  753 + "step": 0
  754 + },
  755 + "mget": {
  756 + "arity": -2,
  757 + "flags": [
  758 + "readonly"
  759 + ],
  760 + "keyStart": 1,
  761 + "keyStop": -1,
  762 + "step": 1
  763 + },
  764 + "migrate": {
  765 + "arity": -6,
  766 + "flags": [
  767 + "write",
  768 + "movablekeys"
  769 + ],
  770 + "keyStart": 0,
  771 + "keyStop": 0,
  772 + "step": 0
  773 + },
  774 + "module": {
  775 + "arity": -2,
  776 + "flags": [
  777 + "admin",
  778 + "noscript"
  779 + ],
  780 + "keyStart": 1,
  781 + "keyStop": 1,
  782 + "step": 1
  783 + },
  784 + "monitor": {
  785 + "arity": 1,
  786 + "flags": [
  787 + "admin",
  788 + "noscript"
  789 + ],
  790 + "keyStart": 0,
  791 + "keyStop": 0,
  792 + "step": 0
  793 + },
  794 + "move": {
  795 + "arity": 3,
  796 + "flags": [
  797 + "write",
  798 + "fast"
  799 + ],
  800 + "keyStart": 1,
  801 + "keyStop": 1,
  802 + "step": 1
  803 + },
  804 + "mset": {
  805 + "arity": -3,
  806 + "flags": [
  807 + "write",
  808 + "denyoom"
  809 + ],
  810 + "keyStart": 1,
  811 + "keyStop": -1,
  812 + "step": 2
  813 + },
  814 + "msetnx": {
  815 + "arity": -3,
  816 + "flags": [
  817 + "write",
  818 + "denyoom"
  819 + ],
  820 + "keyStart": 1,
  821 + "keyStop": -1,
  822 + "step": 2
  823 + },
  824 + "multi": {
  825 + "arity": 1,
  826 + "flags": [
  827 + "noscript",
  828 + "fast"
  829 + ],
  830 + "keyStart": 0,
  831 + "keyStop": 0,
  832 + "step": 0
  833 + },
  834 + "object": {
  835 + "arity": 3,
  836 + "flags": [
  837 + "readonly"
  838 + ],
  839 + "keyStart": 2,
  840 + "keyStop": 2,
  841 + "step": 2
  842 + },
  843 + "persist": {
  844 + "arity": 2,
  845 + "flags": [
  846 + "write",
  847 + "fast"
  848 + ],
  849 + "keyStart": 1,
  850 + "keyStop": 1,
  851 + "step": 1
  852 + },
  853 + "pexpire": {
  854 + "arity": 3,
  855 + "flags": [
  856 + "write",
  857 + "fast"
  858 + ],
  859 + "keyStart": 1,
  860 + "keyStop": 1,
  861 + "step": 1
  862 + },
  863 + "pexpireat": {
  864 + "arity": 3,
  865 + "flags": [
  866 + "write",
  867 + "fast"
  868 + ],
  869 + "keyStart": 1,
  870 + "keyStop": 1,
  871 + "step": 1
  872 + },
  873 + "pfadd": {
  874 + "arity": -2,
  875 + "flags": [
  876 + "write",
  877 + "denyoom",
  878 + "fast"
  879 + ],
  880 + "keyStart": 1,
  881 + "keyStop": 1,
  882 + "step": 1
  883 + },
  884 + "pfcount": {
  885 + "arity": -2,
  886 + "flags": [
  887 + "readonly"
  888 + ],
  889 + "keyStart": 1,
  890 + "keyStop": -1,
  891 + "step": 1
  892 + },
  893 + "pfdebug": {
  894 + "arity": -3,
  895 + "flags": [
  896 + "write"
  897 + ],
  898 + "keyStart": 0,
  899 + "keyStop": 0,
  900 + "step": 0
  901 + },
  902 + "pfmerge": {
  903 + "arity": -2,
  904 + "flags": [
  905 + "write",
  906 + "denyoom"
  907 + ],
  908 + "keyStart": 1,
  909 + "keyStop": -1,
  910 + "step": 1
  911 + },
  912 + "pfselftest": {
  913 + "arity": 1,
  914 + "flags": [
  915 + "admin"
  916 + ],
  917 + "keyStart": 0,
  918 + "keyStop": 0,
  919 + "step": 0
  920 + },
  921 + "ping": {
  922 + "arity": -1,
  923 + "flags": [
  924 + "stale",
  925 + "fast"
  926 + ],
  927 + "keyStart": 0,
  928 + "keyStop": 0,
  929 + "step": 0
  930 + },
  931 + "post": {
  932 + "arity": -1,
  933 + "flags": [
  934 + "loading",
  935 + "stale"
  936 + ],
  937 + "keyStart": 0,
  938 + "keyStop": 0,
  939 + "step": 0
  940 + },
  941 + "psetex": {
  942 + "arity": 4,
  943 + "flags": [
  944 + "write",
  945 + "denyoom"
  946 + ],
  947 + "keyStart": 1,
  948 + "keyStop": 1,
  949 + "step": 1
  950 + },
  951 + "psubscribe": {
  952 + "arity": -2,
  953 + "flags": [
  954 + "pubsub",
  955 + "noscript",
  956 + "loading",
  957 + "stale"
  958 + ],
  959 + "keyStart": 0,
  960 + "keyStop": 0,
  961 + "step": 0
  962 + },
  963 + "psync": {
  964 + "arity": 3,
  965 + "flags": [
  966 + "readonly",
  967 + "admin",
  968 + "noscript"
  969 + ],
  970 + "keyStart": 0,
  971 + "keyStop": 0,
  972 + "step": 0
  973 + },
  974 + "pttl": {
  975 + "arity": 2,
  976 + "flags": [
  977 + "readonly",
  978 + "fast"
  979 + ],
  980 + "keyStart": 1,
  981 + "keyStop": 1,
  982 + "step": 1
  983 + },
  984 + "publish": {
  985 + "arity": 3,
  986 + "flags": [
  987 + "pubsub",
  988 + "loading",
  989 + "stale",
  990 + "fast"
  991 + ],
  992 + "keyStart": 0,
  993 + "keyStop": 0,
  994 + "step": 0
  995 + },
  996 + "pubsub": {
  997 + "arity": -2,
  998 + "flags": [
  999 + "pubsub",
  1000 + "random",
  1001 + "loading",
  1002 + "stale"
  1003 + ],
  1004 + "keyStart": 0,
  1005 + "keyStop": 0,
  1006 + "step": 0
  1007 + },
  1008 + "punsubscribe": {
  1009 + "arity": -1,
  1010 + "flags": [
  1011 + "pubsub",
  1012 + "noscript",
  1013 + "loading",
  1014 + "stale"
  1015 + ],
  1016 + "keyStart": 0,
  1017 + "keyStop": 0,
  1018 + "step": 0
  1019 + },
  1020 + "quit": {
  1021 + "arity": 1,
  1022 + "flags": [
  1023 + "loading",
  1024 + "stale",
  1025 + "readonly"
  1026 + ],
  1027 + "keyStart": 0,
  1028 + "keyStop": 0,
  1029 + "step": 0
  1030 + },
  1031 + "randomkey": {
  1032 + "arity": 1,
  1033 + "flags": [
  1034 + "readonly",
  1035 + "random"
  1036 + ],
  1037 + "keyStart": 0,
  1038 + "keyStop": 0,
  1039 + "step": 0
  1040 + },
  1041 + "readonly": {
  1042 + "arity": 1,
  1043 + "flags": [
  1044 + "fast"
  1045 + ],
  1046 + "keyStart": 0,
  1047 + "keyStop": 0,
  1048 + "step": 0
  1049 + },
  1050 + "readwrite": {
  1051 + "arity": 1,
  1052 + "flags": [
  1053 + "fast"
  1054 + ],
  1055 + "keyStart": 0,
  1056 + "keyStop": 0,
  1057 + "step": 0
  1058 + },
  1059 + "rename": {
  1060 + "arity": 3,
  1061 + "flags": [
  1062 + "write"
  1063 + ],
  1064 + "keyStart": 1,
  1065 + "keyStop": 2,
  1066 + "step": 1
  1067 + },
  1068 + "renamenx": {
  1069 + "arity": 3,
  1070 + "flags": [
  1071 + "write",
  1072 + "fast"
  1073 + ],
  1074 + "keyStart": 1,
  1075 + "keyStop": 2,
  1076 + "step": 1
  1077 + },
  1078 + "replconf": {
  1079 + "arity": -1,
  1080 + "flags": [
  1081 + "admin",
  1082 + "noscript",
  1083 + "loading",
  1084 + "stale"
  1085 + ],
  1086 + "keyStart": 0,
  1087 + "keyStop": 0,
  1088 + "step": 0
  1089 + },
  1090 + "restore": {
  1091 + "arity": -4,
  1092 + "flags": [
  1093 + "write",
  1094 + "denyoom"
  1095 + ],
  1096 + "keyStart": 1,
  1097 + "keyStop": 1,
  1098 + "step": 1
  1099 + },
  1100 + "restore-asking": {
  1101 + "arity": -4,
  1102 + "flags": [
  1103 + "write",
  1104 + "denyoom",
  1105 + "asking"
  1106 + ],
  1107 + "keyStart": 1,
  1108 + "keyStop": 1,
  1109 + "step": 1
  1110 + },
  1111 + "role": {
  1112 + "arity": 1,
  1113 + "flags": [
  1114 + "noscript",
  1115 + "loading",
  1116 + "stale"
  1117 + ],
  1118 + "keyStart": 0,
  1119 + "keyStop": 0,
  1120 + "step": 0
  1121 + },
  1122 + "rpop": {
  1123 + "arity": 2,
  1124 + "flags": [
  1125 + "write",
  1126 + "fast"
  1127 + ],
  1128 + "keyStart": 1,
  1129 + "keyStop": 1,
  1130 + "step": 1
  1131 + },
  1132 + "rpoplpush": {
  1133 + "arity": 3,
  1134 + "flags": [
  1135 + "write",
  1136 + "denyoom"
  1137 + ],
  1138 + "keyStart": 1,
  1139 + "keyStop": 2,
  1140 + "step": 1
  1141 + },
  1142 + "rpush": {
  1143 + "arity": -3,
  1144 + "flags": [
  1145 + "write",
  1146 + "denyoom",
  1147 + "fast"
  1148 + ],
  1149 + "keyStart": 1,
  1150 + "keyStop": 1,
  1151 + "step": 1
  1152 + },
  1153 + "rpushx": {
  1154 + "arity": -3,
  1155 + "flags": [
  1156 + "write",
  1157 + "denyoom",
  1158 + "fast"
  1159 + ],
  1160 + "keyStart": 1,
  1161 + "keyStop": 1,
  1162 + "step": 1
  1163 + },
  1164 + "sadd": {
  1165 + "arity": -3,
  1166 + "flags": [
  1167 + "write",
  1168 + "denyoom",
  1169 + "fast"
  1170 + ],
  1171 + "keyStart": 1,
  1172 + "keyStop": 1,
  1173 + "step": 1
  1174 + },
  1175 + "save": {
  1176 + "arity": 1,
  1177 + "flags": [
  1178 + "admin",
  1179 + "noscript"
  1180 + ],
  1181 + "keyStart": 0,
  1182 + "keyStop": 0,
  1183 + "step": 0
  1184 + },
  1185 + "scan": {
  1186 + "arity": -2,
  1187 + "flags": [
  1188 + "readonly",
  1189 + "random"
  1190 + ],
  1191 + "keyStart": 0,
  1192 + "keyStop": 0,
  1193 + "step": 0
  1194 + },
  1195 + "scard": {
  1196 + "arity": 2,
  1197 + "flags": [
  1198 + "readonly",
  1199 + "fast"
  1200 + ],
  1201 + "keyStart": 1,
  1202 + "keyStop": 1,
  1203 + "step": 1
  1204 + },
  1205 + "script": {
  1206 + "arity": -2,
  1207 + "flags": [
  1208 + "noscript"
  1209 + ],
  1210 + "keyStart": 0,
  1211 + "keyStop": 0,
  1212 + "step": 0
  1213 + },
  1214 + "sdiff": {
  1215 + "arity": -2,
  1216 + "flags": [
  1217 + "readonly",
  1218 + "sort_for_script"
  1219 + ],
  1220 + "keyStart": 1,
  1221 + "keyStop": -1,
  1222 + "step": 1
  1223 + },
  1224 + "sdiffstore": {
  1225 + "arity": -3,
  1226 + "flags": [
  1227 + "write",
  1228 + "denyoom"
  1229 + ],
  1230 + "keyStart": 1,
  1231 + "keyStop": -1,
  1232 + "step": 1
  1233 + },
  1234 + "select": {
  1235 + "arity": 2,
  1236 + "flags": [
  1237 + "loading",
  1238 + "fast"
  1239 + ],
  1240 + "keyStart": 0,
  1241 + "keyStop": 0,
  1242 + "step": 0
  1243 + },
  1244 + "set": {
  1245 + "arity": -3,
  1246 + "flags": [
  1247 + "write",
  1248 + "denyoom"
  1249 + ],
  1250 + "keyStart": 1,
  1251 + "keyStop": 1,
  1252 + "step": 1
  1253 + },
  1254 + "setbit": {
  1255 + "arity": 4,
  1256 + "flags": [
  1257 + "write",
  1258 + "denyoom"
  1259 + ],
  1260 + "keyStart": 1,
  1261 + "keyStop": 1,
  1262 + "step": 1
  1263 + },
  1264 + "setex": {
  1265 + "arity": 4,
  1266 + "flags": [
  1267 + "write",
  1268 + "denyoom"
  1269 + ],
  1270 + "keyStart": 1,
  1271 + "keyStop": 1,
  1272 + "step": 1
  1273 + },
  1274 + "setnx": {
  1275 + "arity": 3,
  1276 + "flags": [
  1277 + "write",
  1278 + "denyoom",
  1279 + "fast"
  1280 + ],
  1281 + "keyStart": 1,
  1282 + "keyStop": 1,
  1283 + "step": 1
  1284 + },
  1285 + "setrange": {
  1286 + "arity": 4,
  1287 + "flags": [
  1288 + "write",
  1289 + "denyoom"
  1290 + ],
  1291 + "keyStart": 1,
  1292 + "keyStop": 1,
  1293 + "step": 1
  1294 + },
  1295 + "shutdown": {
  1296 + "arity": -1,
  1297 + "flags": [
  1298 + "admin",
  1299 + "loading",
  1300 + "stale"
  1301 + ],
  1302 + "keyStart": 0,
  1303 + "keyStop": 0,
  1304 + "step": 0
  1305 + },
  1306 + "sinter": {
  1307 + "arity": -2,
  1308 + "flags": [
  1309 + "readonly",
  1310 + "sort_for_script"
  1311 + ],
  1312 + "keyStart": 1,
  1313 + "keyStop": -1,
  1314 + "step": 1
  1315 + },
  1316 + "sinterstore": {
  1317 + "arity": -3,
  1318 + "flags": [
  1319 + "write",
  1320 + "denyoom"
  1321 + ],
  1322 + "keyStart": 1,
  1323 + "keyStop": -1,
  1324 + "step": 1
  1325 + },
  1326 + "sismember": {
  1327 + "arity": 3,
  1328 + "flags": [
  1329 + "readonly",
  1330 + "fast"
  1331 + ],
  1332 + "keyStart": 1,
  1333 + "keyStop": 1,
  1334 + "step": 1
  1335 + },
  1336 + "slaveof": {
  1337 + "arity": 3,
  1338 + "flags": [
  1339 + "admin",
  1340 + "noscript",
  1341 + "stale"
  1342 + ],
  1343 + "keyStart": 0,
  1344 + "keyStop": 0,
  1345 + "step": 0
  1346 + },
  1347 + "slowlog": {
  1348 + "arity": -2,
  1349 + "flags": [
  1350 + "admin"
  1351 + ],
  1352 + "keyStart": 0,
  1353 + "keyStop": 0,
  1354 + "step": 0
  1355 + },
  1356 + "smembers": {
  1357 + "arity": 2,
  1358 + "flags": [
  1359 + "readonly",
  1360 + "sort_for_script"
  1361 + ],
  1362 + "keyStart": 1,
  1363 + "keyStop": 1,
  1364 + "step": 1
  1365 + },
  1366 + "smove": {
  1367 + "arity": 4,
  1368 + "flags": [
  1369 + "write",
  1370 + "fast"
  1371 + ],
  1372 + "keyStart": 1,
  1373 + "keyStop": 2,
  1374 + "step": 1
  1375 + },
  1376 + "sort": {
  1377 + "arity": -2,
  1378 + "flags": [
  1379 + "write",
  1380 + "denyoom",
  1381 + "movablekeys"
  1382 + ],
  1383 + "keyStart": 1,
  1384 + "keyStop": 1,
  1385 + "step": 1
  1386 + },
  1387 + "spop": {
  1388 + "arity": -2,
  1389 + "flags": [
  1390 + "write",
  1391 + "random",
  1392 + "fast"
  1393 + ],
  1394 + "keyStart": 1,
  1395 + "keyStop": 1,
  1396 + "step": 1
  1397 + },
  1398 + "srandmember": {
  1399 + "arity": -2,
  1400 + "flags": [
  1401 + "readonly",
  1402 + "random"
  1403 + ],
  1404 + "keyStart": 1,
  1405 + "keyStop": 1,
  1406 + "step": 1
  1407 + },
  1408 + "srem": {
  1409 + "arity": -3,
  1410 + "flags": [
  1411 + "write",
  1412 + "fast"
  1413 + ],
  1414 + "keyStart": 1,
  1415 + "keyStop": 1,
  1416 + "step": 1
  1417 + },
  1418 + "sscan": {
  1419 + "arity": -3,
  1420 + "flags": [
  1421 + "readonly",
  1422 + "random"
  1423 + ],
  1424 + "keyStart": 1,
  1425 + "keyStop": 1,
  1426 + "step": 1
  1427 + },
  1428 + "strlen": {
  1429 + "arity": 2,
  1430 + "flags": [
  1431 + "readonly",
  1432 + "fast"
  1433 + ],
  1434 + "keyStart": 1,
  1435 + "keyStop": 1,
  1436 + "step": 1
  1437 + },
  1438 + "subscribe": {
  1439 + "arity": -2,
  1440 + "flags": [
  1441 + "pubsub",
  1442 + "noscript",
  1443 + "loading",
  1444 + "stale"
  1445 + ],
  1446 + "keyStart": 0,
  1447 + "keyStop": 0,
  1448 + "step": 0
  1449 + },
  1450 + "substr": {
  1451 + "arity": 4,
  1452 + "flags": [
  1453 + "readonly"
  1454 + ],
  1455 + "keyStart": 1,
  1456 + "keyStop": 1,
  1457 + "step": 1
  1458 + },
  1459 + "sunion": {
  1460 + "arity": -2,
  1461 + "flags": [
  1462 + "readonly",
  1463 + "sort_for_script"
  1464 + ],
  1465 + "keyStart": 1,
  1466 + "keyStop": -1,
  1467 + "step": 1
  1468 + },
  1469 + "sunionstore": {
  1470 + "arity": -3,
  1471 + "flags": [
  1472 + "write",
  1473 + "denyoom"
  1474 + ],
  1475 + "keyStart": 1,
  1476 + "keyStop": -1,
  1477 + "step": 1
  1478 + },
  1479 + "swapdb": {
  1480 + "arity": 3,
  1481 + "flags": [
  1482 + "write",
  1483 + "fast"
  1484 + ],
  1485 + "keyStart": 0,
  1486 + "keyStop": 0,
  1487 + "step": 0
  1488 + },
  1489 + "sync": {
  1490 + "arity": 1,
  1491 + "flags": [
  1492 + "readonly",
  1493 + "admin",
  1494 + "noscript"
  1495 + ],
  1496 + "keyStart": 0,
  1497 + "keyStop": 0,
  1498 + "step": 0
  1499 + },
  1500 + "time": {
  1501 + "arity": 1,
  1502 + "flags": [
  1503 + "random",
  1504 + "fast"
  1505 + ],
  1506 + "keyStart": 0,
  1507 + "keyStop": 0,
  1508 + "step": 0
  1509 + },
  1510 + "touch": {
  1511 + "arity": -2,
  1512 + "flags": [
  1513 + "readonly",
  1514 + "fast"
  1515 + ],
  1516 + "keyStart": 1,
  1517 + "keyStop": 1,
  1518 + "step": 1
  1519 + },
  1520 + "ttl": {
  1521 + "arity": 2,
  1522 + "flags": [
  1523 + "readonly",
  1524 + "fast"
  1525 + ],
  1526 + "keyStart": 1,
  1527 + "keyStop": 1,
  1528 + "step": 1
  1529 + },
  1530 + "type": {
  1531 + "arity": 2,
  1532 + "flags": [
  1533 + "readonly",
  1534 + "fast"
  1535 + ],
  1536 + "keyStart": 1,
  1537 + "keyStop": 1,
  1538 + "step": 1
  1539 + },
  1540 + "unlink": {
  1541 + "arity": -2,
  1542 + "flags": [
  1543 + "write",
  1544 + "fast"
  1545 + ],
  1546 + "keyStart": 1,
  1547 + "keyStop": -1,
  1548 + "step": 1
  1549 + },
  1550 + "unsubscribe": {
  1551 + "arity": -1,
  1552 + "flags": [
  1553 + "pubsub",
  1554 + "noscript",
  1555 + "loading",
  1556 + "stale"
  1557 + ],
  1558 + "keyStart": 0,
  1559 + "keyStop": 0,
  1560 + "step": 0
  1561 + },
  1562 + "unwatch": {
  1563 + "arity": 1,
  1564 + "flags": [
  1565 + "noscript",
  1566 + "fast"
  1567 + ],
  1568 + "keyStart": 0,
  1569 + "keyStop": 0,
  1570 + "step": 0
  1571 + },
  1572 + "wait": {
  1573 + "arity": 3,
  1574 + "flags": [
  1575 + "noscript"
  1576 + ],
  1577 + "keyStart": 0,
  1578 + "keyStop": 0,
  1579 + "step": 0
  1580 + },
  1581 + "watch": {
  1582 + "arity": -2,
  1583 + "flags": [
  1584 + "noscript",
  1585 + "fast"
  1586 + ],
  1587 + "keyStart": 1,
  1588 + "keyStop": -1,
  1589 + "step": 1
  1590 + },
  1591 + "zadd": {
  1592 + "arity": -4,
  1593 + "flags": [
  1594 + "write",
  1595 + "denyoom",
  1596 + "fast"
  1597 + ],
  1598 + "keyStart": 1,
  1599 + "keyStop": 1,
  1600 + "step": 1
  1601 + },
  1602 + "zcard": {
  1603 + "arity": 2,
  1604 + "flags": [
  1605 + "readonly",
  1606 + "fast"
  1607 + ],
  1608 + "keyStart": 1,
  1609 + "keyStop": 1,
  1610 + "step": 1
  1611 + },
  1612 + "zcount": {
  1613 + "arity": 4,
  1614 + "flags": [
  1615 + "readonly",
  1616 + "fast"
  1617 + ],
  1618 + "keyStart": 1,
  1619 + "keyStop": 1,
  1620 + "step": 1
  1621 + },
  1622 + "zincrby": {
  1623 + "arity": 4,
  1624 + "flags": [
  1625 + "write",
  1626 + "denyoom",
  1627 + "fast"
  1628 + ],
  1629 + "keyStart": 1,
  1630 + "keyStop": 1,
  1631 + "step": 1
  1632 + },
  1633 + "zinterstore": {
  1634 + "arity": -4,
  1635 + "flags": [
  1636 + "write",
  1637 + "denyoom",
  1638 + "movablekeys"
  1639 + ],
  1640 + "keyStart": 0,
  1641 + "keyStop": 0,
  1642 + "step": 0
  1643 + },
  1644 + "zlexcount": {
  1645 + "arity": 4,
  1646 + "flags": [
  1647 + "readonly",
  1648 + "fast"
  1649 + ],
  1650 + "keyStart": 1,
  1651 + "keyStop": 1,
  1652 + "step": 1
  1653 + },
  1654 + "zrange": {
  1655 + "arity": -4,
  1656 + "flags": [
  1657 + "readonly"
  1658 + ],
  1659 + "keyStart": 1,
  1660 + "keyStop": 1,
  1661 + "step": 1
  1662 + },
  1663 + "zrangebylex": {
  1664 + "arity": -4,
  1665 + "flags": [
  1666 + "readonly"
  1667 + ],
  1668 + "keyStart": 1,
  1669 + "keyStop": 1,
  1670 + "step": 1
  1671 + },
  1672 + "zrangebyscore": {
  1673 + "arity": -4,
  1674 + "flags": [
  1675 + "readonly"
  1676 + ],
  1677 + "keyStart": 1,
  1678 + "keyStop": 1,
  1679 + "step": 1
  1680 + },
  1681 + "zrank": {
  1682 + "arity": 3,
  1683 + "flags": [
  1684 + "readonly",
  1685 + "fast"
  1686 + ],
  1687 + "keyStart": 1,
  1688 + "keyStop": 1,
  1689 + "step": 1
  1690 + },
  1691 + "zrem": {
  1692 + "arity": -3,
  1693 + "flags": [
  1694 + "write",
  1695 + "fast"
  1696 + ],
  1697 + "keyStart": 1,
  1698 + "keyStop": 1,
  1699 + "step": 1
  1700 + },
  1701 + "zremrangebylex": {
  1702 + "arity": 4,
  1703 + "flags": [
  1704 + "write"
  1705 + ],
  1706 + "keyStart": 1,
  1707 + "keyStop": 1,
  1708 + "step": 1
  1709 + },
  1710 + "zremrangebyrank": {
  1711 + "arity": 4,
  1712 + "flags": [
  1713 + "write"
  1714 + ],
  1715 + "keyStart": 1,
  1716 + "keyStop": 1,
  1717 + "step": 1
  1718 + },
  1719 + "zremrangebyscore": {
  1720 + "arity": 4,
  1721 + "flags": [
  1722 + "write"
  1723 + ],
  1724 + "keyStart": 1,
  1725 + "keyStop": 1,
  1726 + "step": 1
  1727 + },
  1728 + "zrevrange": {
  1729 + "arity": -4,
  1730 + "flags": [
  1731 + "readonly"
  1732 + ],
  1733 + "keyStart": 1,
  1734 + "keyStop": 1,
  1735 + "step": 1
  1736 + },
  1737 + "zrevrangebylex": {
  1738 + "arity": -4,
  1739 + "flags": [
  1740 + "readonly"
  1741 + ],
  1742 + "keyStart": 1,
  1743 + "keyStop": 1,
  1744 + "step": 1
  1745 + },
  1746 + "zrevrangebyscore": {
  1747 + "arity": -4,
  1748 + "flags": [
  1749 + "readonly"
  1750 + ],
  1751 + "keyStart": 1,
  1752 + "keyStop": 1,
  1753 + "step": 1
  1754 + },
  1755 + "zrevrank": {
  1756 + "arity": 3,
  1757 + "flags": [
  1758 + "readonly",
  1759 + "fast"
  1760 + ],
  1761 + "keyStart": 1,
  1762 + "keyStop": 1,
  1763 + "step": 1
  1764 + },
  1765 + "zscan": {
  1766 + "arity": -3,
  1767 + "flags": [
  1768 + "readonly",
  1769 + "random"
  1770 + ],
  1771 + "keyStart": 1,
  1772 + "keyStop": 1,
  1773 + "step": 1
  1774 + },
  1775 + "zscore": {
  1776 + "arity": 3,
  1777 + "flags": [
  1778 + "readonly",
  1779 + "fast"
  1780 + ],
  1781 + "keyStart": 1,
  1782 + "keyStop": 1,
  1783 + "step": 1
  1784 + },
  1785 + "zunionstore": {
  1786 + "arity": -4,
  1787 + "flags": [
  1788 + "write",
  1789 + "denyoom",
  1790 + "movablekeys"
  1791 + ],
  1792 + "keyStart": 0,
  1793 + "keyStop": 0,
  1794 + "step": 0
  1795 + }
  1796 +}
  1 +'use strict'
  2 +
  3 +var commands = require('./commands.json')
  4 +
  5 +/**
  6 + * Redis command list
  7 + *
  8 + * All commands are lowercased.
  9 + *
  10 + * @var {string[]}
  11 + * @public
  12 + */
  13 +exports.list = Object.keys(commands)
  14 +
  15 +var flags = {}
  16 +exports.list.forEach(function (commandName) {
  17 + flags[commandName] = commands[commandName].flags.reduce(function (flags, flag) {
  18 + flags[flag] = true
  19 + return flags
  20 + }, {})
  21 +})
  22 +/**
  23 + * Check if the command exists
  24 + *
  25 + * @param {string} commandName - the command name
  26 + * @return {boolean} result
  27 + * @public
  28 + */
  29 +exports.exists = function (commandName) {
  30 + return Boolean(commands[commandName])
  31 +}
  32 +
  33 +/**
  34 + * Check if the command has the flag
  35 + *
  36 + * Some of possible flags: readonly, noscript, loading
  37 + * @param {string} commandName - the command name
  38 + * @param {string} flag - the flag to check
  39 + * @return {boolean} result
  40 + * @public
  41 + */
  42 +exports.hasFlag = function (commandName, flag) {
  43 + if (!flags[commandName]) {
  44 + throw new Error('Unknown command ' + commandName)
  45 + }
  46 +
  47 + return Boolean(flags[commandName][flag])
  48 +}
  49 +
  50 +/**
  51 + * Get indexes of keys in the command arguments
  52 + *
  53 + * @param {string} commandName - the command name
  54 + * @param {string[]} args - the arguments of the command
  55 + * @param {object} [options] - options
  56 + * @param {boolean} [options.parseExternalKey] - parse external keys
  57 + * @return {number[]} - the list of the index
  58 + * @public
  59 + *
  60 + * @example
  61 + * ```javascript
  62 + * getKeyIndexes('set', ['key', 'value']) // [0]
  63 + * getKeyIndexes('mget', ['key1', 'key2']) // [0, 1]
  64 + * ```
  65 + */
  66 +exports.getKeyIndexes = function (commandName, args, options) {
  67 + var command = commands[commandName]
  68 + if (!command) {
  69 + throw new Error('Unknown command ' + commandName)
  70 + }
  71 +
  72 + if (!Array.isArray(args)) {
  73 + throw new Error('Expect args to be an array')
  74 + }
  75 +
  76 + var keys = []
  77 + var i, keyStart, keyStop, parseExternalKey
  78 + switch (commandName) {
  79 + case 'zunionstore':
  80 + case 'zinterstore':
  81 + keys.push(0)
  82 + // fall through
  83 + case 'eval':
  84 + case 'evalsha':
  85 + keyStop = Number(args[1]) + 2
  86 + for (i = 2; i < keyStop; i++) {
  87 + keys.push(i)
  88 + }
  89 + break
  90 + case 'sort':
  91 + parseExternalKey = options && options.parseExternalKey
  92 + keys.push(0)
  93 + for (i = 1; i < args.length - 1; i++) {
  94 + if (typeof args[i] !== 'string') {
  95 + continue
  96 + }
  97 + var directive = args[i].toUpperCase()
  98 + if (directive === 'GET') {
  99 + i += 1
  100 + if (args[i] !== '#') {
  101 + if (parseExternalKey) {
  102 + keys.push([i, getExternalKeyNameLength(args[i])])
  103 + } else {
  104 + keys.push(i)
  105 + }
  106 + }
  107 + } else if (directive === 'BY') {
  108 + i += 1
  109 + if (parseExternalKey) {
  110 + keys.push([i, getExternalKeyNameLength(args[i])])
  111 + } else {
  112 + keys.push(i)
  113 + }
  114 + } else if (directive === 'STORE') {
  115 + i += 1
  116 + keys.push(i)
  117 + }
  118 + }
  119 + break
  120 + case 'migrate':
  121 + if (args[2] === '') {
  122 + for (i = 5; i < args.length - 1; i++) {
  123 + if (args[i].toUpperCase() === 'KEYS') {
  124 + for (var j = i + 1; j < args.length; j++) {
  125 + keys.push(j)
  126 + }
  127 + break
  128 + }
  129 + }
  130 + } else {
  131 + keys.push(2)
  132 + }
  133 + break
  134 + default:
  135 + // step has to be at least one in this case, otherwise the command does not contain a key
  136 + if (command.step > 0) {
  137 + keyStart = command.keyStart - 1
  138 + keyStop = command.keyStop > 0 ? command.keyStop : args.length + command.keyStop + 1
  139 + for (i = keyStart; i < keyStop; i += command.step) {
  140 + keys.push(i)
  141 + }
  142 + }
  143 + break
  144 + }
  145 +
  146 + return keys
  147 +}
  148 +
  149 +function getExternalKeyNameLength (key) {
  150 + if (typeof key !== 'string') {
  151 + key = String(key)
  152 + }
  153 + var hashPos = key.indexOf('->')
  154 + return hashPos === -1 ? key.length : hashPos
  155 +}
  1 +{
  2 + "_args": [
  3 + [
  4 + {
  5 + "raw": "redis-commands@^1.2.0",
  6 + "scope": null,
  7 + "escapedName": "redis-commands",
  8 + "name": "redis-commands",
  9 + "rawSpec": "^1.2.0",
  10 + "spec": ">=1.2.0 <2.0.0",
  11 + "type": "range"
  12 + },
  13 + "/Users/fzy/project/koa2_Sequelize_project/node_modules/redis"
  14 + ]
  15 + ],
  16 + "_from": "redis-commands@>=1.2.0 <2.0.0",
  17 + "_id": "redis-commands@1.3.1",
  18 + "_inCache": true,
  19 + "_location": "/redis-commands",
  20 + "_nodeVersion": "7.4.0",
  21 + "_npmOperationalInternal": {
  22 + "host": "packages-12-west.internal.npmjs.com",
  23 + "tmp": "tmp/redis-commands-1.3.1.tgz_1485363355063_0.7909097610972822"
  24 + },
  25 + "_npmUser": {
  26 + "name": "bridgear",
  27 + "email": "ruben@bridgewater.de"
  28 + },
  29 + "_npmVersion": "4.0.5",
  30 + "_phantomChildren": {},
  31 + "_requested": {
  32 + "raw": "redis-commands@^1.2.0",
  33 + "scope": null,
  34 + "escapedName": "redis-commands",
  35 + "name": "redis-commands",
  36 + "rawSpec": "^1.2.0",
  37 + "spec": ">=1.2.0 <2.0.0",
  38 + "type": "range"
  39 + },
  40 + "_requiredBy": [
  41 + "/redis"
  42 + ],
  43 + "_resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.1.tgz",
  44 + "_shasum": "81d826f45fa9c8b2011f4cd7a0fe597d241d442b",
  45 + "_shrinkwrap": null,
  46 + "_spec": "redis-commands@^1.2.0",
  47 + "_where": "/Users/fzy/project/koa2_Sequelize_project/node_modules/redis",
  48 + "author": {
  49 + "name": "luin",
  50 + "email": "i@zihua.li",
  51 + "url": "http://zihua.li"
  52 + },
  53 + "bugs": {
  54 + "url": "https://github.com/NodeRedis/redis-commonds/issues"
  55 + },
  56 + "dependencies": {},
  57 + "description": "Redis commands",
  58 + "devDependencies": {
  59 + "chai": "^3.4.0",
  60 + "codeclimate-test-reporter": "^0.4.0",
  61 + "ioredis": "^2.0.0",
  62 + "istanbul": "^0.4.3",
  63 + "json-stable-stringify": "^1.0.0",
  64 + "mocha": "^3.0.0",
  65 + "snazzy": "^6.0.0",
  66 + "standard": "^8.0.0"
  67 + },
  68 + "directories": {},
  69 + "dist": {
  70 + "shasum": "81d826f45fa9c8b2011f4cd7a0fe597d241d442b",
  71 + "tarball": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.1.tgz"
  72 + },
  73 + "gitHead": "3c87f94d94fcf66ca0c77101a6d36fad32134326",
  74 + "homepage": "https://github.com/NodeRedis/redis-commonds",
  75 + "keywords": [
  76 + "redis",
  77 + "commands",
  78 + "prefix"
  79 + ],
  80 + "license": "MIT",
  81 + "main": "index.js",
  82 + "maintainers": [
  83 + {
  84 + "name": "bridgear",
  85 + "email": "ruben@bridgewater.de"
  86 + },
  87 + {
  88 + "name": "luin",
  89 + "email": "i@zihua.li"
  90 + }
  91 + ],
  92 + "name": "redis-commands",
  93 + "optionalDependencies": {},
  94 + "readme": "# Redis Commands\n\n[![Build Status](https://travis-ci.org/NodeRedis/redis-commands.png?branch=master)](https://travis-ci.org/NodeRedis/redis-commands)\n[![Code Climate](https://codeclimate.com/github/NodeRedis/redis-commands/badges/gpa.svg)](https://codeclimate.com/github/NodeRedis/redis-commands)\n[![Test Coverage](https://codeclimate.com/github/NodeRedis/redis-commands/badges/coverage.svg)](https://codeclimate.com/github/NodeRedis/redis-commands/coverage)\n\nThis module exports all the commands that Redis supports.\n\n## Install\n\n```shell\n$ npm install redis-commands\n```\n\n## Usage\n\n```javascript\nvar commands = require('redis-commands');\n```\n\n`.list` is an array contains all the lowercased commands:\n\n```javascript\ncommands.list.forEach(function (command) {\n console.log(command);\n});\n```\n\n`.exists()` is used to check if the command exists:\n\n```javascript\ncommands.exists('set') // true\ncommands.exists('other-command') // false\n```\n\n`.hasFlag()` is used to check if the command has the flag:\n\n```javascript\ncommands.hasFlag('set', 'readonly') // false\n```\n\n`.getKeyIndexes()` is used to get the indexes of keys in the command arguments:\n\n```javascript\ncommands.getKeyIndexes('set', ['key', 'value']) // [0]\ncommands.getKeyIndexes('mget', ['key1', 'key2']) // [0, 1]\n```\n\n## Acknowledgment\n\nThank [@Yuan Chuan](https://github.com/yuanchuan) for the package name. The original redis-commands is renamed to [@yuanchuan/redis-commands](https://www.npmjs.com/package/@yuanchuan/redis-commands).\n",
  95 + "readmeFilename": "README.md",
  96 + "repository": {
  97 + "type": "git",
  98 + "url": "git+https://github.com/NodeRedis/redis-commands.git"
  99 + },
  100 + "scripts": {
  101 + "build": "node tools/build",
  102 + "coverage": "node ./node_modules/istanbul/lib/cli.js cover --preserve-comments ./node_modules/mocha/bin/_mocha -- -R spec",
  103 + "coverage:check": "node ./node_modules/istanbul/lib/cli.js check-coverage --branch 100 --statement 100",
  104 + "lint": "standard --fix --verbose | snazzy",
  105 + "posttest": "npm run coverage && npm run coverage:check",
  106 + "pretest": "npm run lint",
  107 + "test": "mocha"
  108 + },
  109 + "version": "1.3.1"
  110 +}
  1 +'use strict'
  2 +
  3 +/* global describe, it */
  4 +
  5 +var commands = require('..')
  6 +var expect = require('chai').expect
  7 +
  8 +describe('redis-commands', function () {
  9 + describe('.list', function () {
  10 + it('should be an array', function () {
  11 + expect(commands.list).to.be.instanceof(Array)
  12 + })
  13 +
  14 + it('should ensure every command is lowercase', function () {
  15 + commands.list.forEach(function (command) {
  16 + expect(command.toLowerCase()).to.eql(command)
  17 + })
  18 + })
  19 +
  20 + it('should ensure quit command is added to the commands list', function () {
  21 + expect(commands.list.indexOf('quit')).not.to.eql(-1)
  22 + })
  23 +
  24 + it('should not contain multi-word commands', function () {
  25 + commands.list.forEach(function (command) {
  26 + expect(command.indexOf(' ')).to.eql(-1)
  27 + })
  28 + })
  29 + })
  30 +
  31 + describe('.exists()', function () {
  32 + it('should return true for existing commands', function () {
  33 + expect(commands.exists('set')).to.eql(true)
  34 + expect(commands.exists('get')).to.eql(true)
  35 + expect(commands.exists('cluster')).to.eql(true)
  36 + expect(commands.exists('quit')).to.eql(true)
  37 + expect(commands.exists('config')).to.eql(true)
  38 + })
  39 +
  40 + it('should return false for non-existing commands', function () {
  41 + expect(commands.exists('SET')).to.eql(false)
  42 + expect(commands.exists('set get')).to.eql(false)
  43 + expect(commands.exists('other-command')).to.eql(false)
  44 + })
  45 + })
  46 +
  47 + describe('.hasFlag()', function () {
  48 + it('should return true if the command has the flag', function () {
  49 + expect(commands.hasFlag('set', 'write')).to.eql(true)
  50 + expect(commands.hasFlag('set', 'denyoom')).to.eql(true)
  51 + expect(commands.hasFlag('select', 'fast')).to.eql(true)
  52 + })
  53 +
  54 + it('should return false otherwise', function () {
  55 + expect(commands.hasFlag('set', 'fast')).to.eql(false)
  56 + expect(commands.hasFlag('set', 'readonly')).to.eql(false)
  57 + expect(commands.hasFlag('select', 'denyoom')).to.eql(false)
  58 + expect(commands.hasFlag('quit', 'denyoom')).to.eql(false)
  59 + })
  60 +
  61 + it('should throw on unknown commands', function () {
  62 + expect(function () { commands.hasFlag('UNKNOWN') }).to.throw(Error)
  63 + })
  64 + })
  65 +
  66 + describe('.getKeyIndexes()', function () {
  67 + var index = commands.getKeyIndexes
  68 +
  69 + it('should throw on unknown commands', function () {
  70 + expect(function () { index('UNKNOWN') }).to.throw(Error)
  71 + })
  72 +
  73 + it('should throw on faulty args', function () {
  74 + expect(function () { index('get', 'foo') }).to.throw(Error)
  75 + })
  76 +
  77 + it('should return an empty array if no keys exist', function () {
  78 + expect(index('auth', [])).to.eql([])
  79 + })
  80 +
  81 + it('should return key indexes', function () {
  82 + expect(index('set', ['foo', 'bar'])).to.eql([0])
  83 + expect(index('del', ['foo'])).to.eql([0])
  84 + expect(index('get', ['foo'])).to.eql([0])
  85 + expect(index('mget', ['foo', 'bar'])).to.eql([0, 1])
  86 + expect(index('mset', ['foo', 'v1', 'bar', 'v2'])).to.eql([0, 2])
  87 + expect(index('hmset', ['key', 'foo', 'v1', 'bar', 'v2'])).to.eql([0])
  88 + expect(index('blpop', ['key1', 'key2', '17'])).to.eql([0, 1])
  89 + expect(index('evalsha', ['23123', '2', 'foo', 'bar', 'zoo'])).to.eql([2, 3])
  90 + expect(index('sort', ['key'])).to.eql([0])
  91 + expect(index('zunionstore', ['out', '2', 'zset1', 'zset2', 'WEIGHTS', '2', '3'])).to.eql([0, 2, 3])
  92 + expect(index('migrate', ['127.0.0.1', 6379, 'foo', 0, 0, 'COPY'])).to.eql([2])
  93 + expect(index('migrate', ['127.0.0.1', 6379, '', 0, 0, 'REPLACE', 'KEYS', 'foo', 'bar'])).to.eql([7, 8])
  94 + expect(index('migrate', ['127.0.0.1', 6379, '', 0, 0, 'KEYS', 'foo', 'bar'])).to.eql([6, 7])
  95 + })
  96 +
  97 + it('should support numeric argument', function () {
  98 + expect(index('evalsha', ['23123', 2, 'foo', 'bar', 'zoo'])).to.eql([2, 3])
  99 + expect(index('zinterstore', ['out', 2, 'zset1', 'zset2', 'WEIGHTS', 2, 3])).to.eql([0, 2, 3])
  100 + })
  101 +
  102 + describe('disable parseExternalKey', function () {
  103 + it('should not parse external keys', function () {
  104 + expect(index('sort', ['key', 'BY', 'hash:*->field'])).to.eql([0, 2])
  105 + expect(index('sort', ['key', 'BY', 'hash:*->field', 'LIMIT', 2, 3, 'GET', 'gk', 'GET', '#', 'Get', 'gh->f*', 'DESC', 'ALPHA', 'STORE', 'store'])).to.eql([0, 2, 7, 11, 15])
  106 + })
  107 + })
  108 +
  109 + describe('enable parseExternalKey', function () {
  110 + it('should parse external keys', function () {
  111 + expect(index('sort', ['key', 'BY', 'hash:*->field'], {
  112 + parseExternalKey: true
  113 + })).to.eql([0, [2, 6]])
  114 + expect(index('sort', ['key', 'BY', 'hash:*->field', 'LIMIT', 2, 3, 'GET', new Buffer('gk'), 'GET', '#', 'Get', 'gh->f*', 'DESC', 'ALPHA', 'STORE', 'store'], {
  115 + parseExternalKey: true
  116 + })).to.eql([0, [2, 6], [7, 2], [11, 2], 15])
  117 + })
  118 + })
  119 + })
  120 +})
  1 +var fs = require('fs')
  2 +var path = require('path')
  3 +var stringify = require('json-stable-stringify')
  4 +var commandPath = path.join(__dirname, '..', 'commands.json')
  5 +var redisCommands = require('../')
  6 +
  7 +var Redis = require('ioredis')
  8 +var redis = new Redis(process.env.REDIS_URI)
  9 +
  10 +redis.command().then(function (res) {
  11 + redis.disconnect()
  12 +
  13 + // Find all special handled cases
  14 + var movableKeys = String(redisCommands.getKeyIndexes).match(/case '[a-z-]+':/g).map(function (entry) {
  15 + return entry.replace(/^case '|':$/g, '')
  16 + })
  17 +
  18 + var commands = res.reduce(function (prev, current) {
  19 + var currentCommandPos = movableKeys.indexOf(current[0])
  20 + if (currentCommandPos !== -1 && current[2].indexOf('movablekeys') !== -1) {
  21 + movableKeys.splice(currentCommandPos, 1)
  22 + }
  23 + // https://github.com/antirez/redis/issues/2598
  24 + if (current[0] === 'brpop' && current[4] === 1) {
  25 + current[4] = -2
  26 + }
  27 + prev[current[0]] = {
  28 + arity: current[1] || 1, // https://github.com/antirez/redis/pull/2986
  29 + flags: current[2],
  30 + keyStart: current[3],
  31 + keyStop: current[4],
  32 + step: current[5]
  33 + }
  34 + return prev
  35 + }, {})
  36 +
  37 + // Future proof. Redis might implement this at some point
  38 + // https://github.com/antirez/redis/pull/2982
  39 + if (!commands.quit) {
  40 + commands.quit = {
  41 + arity: 1,
  42 + flags: [
  43 + 'loading',
  44 + 'stale',
  45 + 'readonly'
  46 + ],
  47 + keyStart: 0,
  48 + keyStop: 0,
  49 + step: 0
  50 + }
  51 + }
  52 +
  53 + if (movableKeys.length !== 0) {
  54 + throw new Error('Not all commands (\'' + movableKeys.join('\', \'') + '\') with the "movablekeys" flag are handled in the code')
  55 + }
  56 +
  57 + // Use json-stable-stringify instead fo JSON.stringify
  58 + // for easier diffing
  59 + var content = stringify(commands, { space: ' ' })
  60 +
  61 + fs.writeFile(commandPath, content)
  62 +})
  1 +# IntelliJ project files
  2 +.idea
  3 +*.iml
  4 +out
  5 +gen
  6 +
  7 +# Unrelevant files and folders
  8 +benchmark
  9 +coverage
  10 +test
  11 +.travis.yml
  12 +.gitignore
  13 +*.log
  14 +.vscode
  15 +.codeclimate.yml
  1 +The MIT License (MIT)
  2 +
  3 +Copyright (c) 2015 NodeRedis
  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 +
  1 +[![Build Status](https://travis-ci.org/NodeRedis/node-redis-parser.png?branch=master)](https://travis-ci.org/NodeRedis/node-redis-parser)
  2 +[![Test Coverage](https://codeclimate.com/github/NodeRedis/node-redis-parser/badges/coverage.svg)](https://codeclimate.com/github/NodeRedis/node-redis-parser/coverage)
  3 +[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
  4 +
  5 +# redis-parser
  6 +
  7 +A high performance javascript redis parser built for [node_redis](https://github.com/NodeRedis/node_redis) and [ioredis](https://github.com/luin/ioredis). Parses all [RESP](http://redis.io/topics/protocol) data.
  8 +
  9 +## Install
  10 +
  11 +Install with [NPM](https://npmjs.org/):
  12 +
  13 + npm install redis-parser
  14 +
  15 +## Usage
  16 +
  17 +```js
  18 +var Parser = require('redis-parser');
  19 +
  20 +var myParser = new Parser(options);
  21 +```
  22 +
  23 +### Options
  24 +
  25 +* `returnReply`: *function*; mandatory
  26 +* `returnError`: *function*; mandatory
  27 +* `returnFatalError`: *function*; optional, defaults to the returnError function
  28 +* `returnBuffers`: *boolean*; optional, defaults to false
  29 +* `stringNumbers`: *boolean*; optional, defaults to false
  30 +
  31 +### Functions
  32 +
  33 +* `reset()`: reset the parser to it's initial state
  34 +* `setReturnBuffers(boolean)`: (JSParser only) set the returnBuffers option on/off without resetting the parser
  35 +* `setStringNumbers(boolean)`: (JSParser only) set the stringNumbers option on/off without resetting the parser
  36 +
  37 +### Error classes
  38 +
  39 +* `RedisError` sub class of Error
  40 +* `ReplyError` sub class of RedisError
  41 +* `ParserError` sub class of RedisError
  42 +
  43 +All Redis errors will be returned as `ReplyErrors` while a parser error is returned as `ParserError`.
  44 +All error classes are exported by the parser.
  45 +
  46 +### Example
  47 +
  48 +```js
  49 +var Parser = require("redis-parser");
  50 +
  51 +function Library () {}
  52 +
  53 +Library.prototype.returnReply = function (reply) { ... }
  54 +Library.prototype.returnError = function (err) { ... }
  55 +Library.prototype.returnFatalError = function (err) { ... }
  56 +
  57 +var lib = new Library();
  58 +
  59 +var parser = new Parser({
  60 + returnReply: function(reply) {
  61 + lib.returnReply(reply);
  62 + },
  63 + returnError: function(err) {
  64 + lib.returnError(err);
  65 + },
  66 + returnFatalError: function (err) {
  67 + lib.returnFatalError(err);
  68 + }
  69 +});
  70 +
  71 +Library.prototype.streamHandler = function () {
  72 + this.stream.on('data', function (buffer) {
  73 + // Here the data (e.g. `new Buffer('$5\r\nHello\r\n'`)) is passed to the parser and the result is passed to either function depending on the provided data.
  74 + parser.execute(buffer);
  75 + });
  76 +};
  77 +```
  78 +You do not have to use the returnFatalError function. Fatal errors will be returned in the normal error function in that case.
  79 +
  80 +And if you want to return buffers instead of strings, you can do this by adding the `returnBuffers` option.
  81 +
  82 +If you handle with big numbers that are to large for JS (Number.MAX_SAFE_INTEGER === 2^53 - 16) please use the `stringNumbers` option. That way all numbers are going to be returned as String and you can handle them safely.
  83 +
  84 +```js
  85 +// Same functions as in the first example
  86 +
  87 +var parser = new Parser({
  88 + returnReply: function(reply) {
  89 + lib.returnReply(reply);
  90 + },
  91 + returnError: function(err) {
  92 + lib.returnError(err);
  93 + },
  94 + returnBuffers: true, // All strings are returned as Buffer e.g. <Buffer 48 65 6c 6c 6f>
  95 + stringNumbers: true // All numbers are returned as String
  96 +});
  97 +
  98 +// The streamHandler as above
  99 +```
  100 +
  101 +## Protocol errors
  102 +
  103 +To handle protocol errors (this is very unlikely to happen) gracefully you should add the returnFatalError option, reject any still running command (they might have been processed properly but the reply is just wrong), destroy the socket and reconnect. Note that while doing this no new command may be added, so all new commands have to be buffered in the meantime, otherwise a chunk might still contain partial data of a following command that was already processed properly but answered in the same chunk as the command that resulted in the protocol error.
  104 +
  105 +## Contribute
  106 +
  107 +The parser is highly optimized but there may still be further optimizations possible.
  108 +
  109 + npm install
  110 + npm test
  111 + npm run benchmark
  112 +
  113 +Currently the benchmark compares the performance against the hiredis parser:
  114 +
  115 + HIREDIS: $ multiple chunks in a bulk string x 859,880 ops/sec ±1.22% (82 runs sampled)
  116 + HIREDIS BUF: $ multiple chunks in a bulk string x 608,869 ops/sec ±1.72% (85 runs sampled)
  117 + JS PARSER: $ multiple chunks in a bulk string x 910,590 ops/sec ±0.87% (89 runs sampled)
  118 + JS PARSER BUF: $ multiple chunks in a bulk string x 1,299,507 ops/sec ±2.18% (84 runs sampled)
  119 +
  120 + HIREDIS: + multiple chunks in a string x 1,787,203 ops/sec ±0.58% (96 runs sampled)
  121 + HIREDIS BUF: + multiple chunks in a string x 943,584 ops/sec ±1.62% (87 runs sampled)
  122 + JS PARSER: + multiple chunks in a string x 2,008,264 ops/sec ±1.01% (91 runs sampled)
  123 + JS PARSER BUF: + multiple chunks in a string x 2,045,546 ops/sec ±0.78% (91 runs sampled)
  124 +
  125 + HIREDIS: $ 4mb bulk string x 310 ops/sec ±1.58% (75 runs sampled)
  126 + HIREDIS BUF: $ 4mb bulk string x 471 ops/sec ±2.28% (78 runs sampled)
  127 + JS PARSER: $ 4mb bulk string x 747 ops/sec ±2.43% (85 runs sampled)
  128 + JS PARSER BUF: $ 4mb bulk string x 846 ops/sec ±5.52% (72 runs sampled)
  129 +
  130 + HIREDIS: + simple string x 2,324,866 ops/sec ±1.61% (90 runs sampled)
  131 + HIREDIS BUF: + simple string x 1,085,823 ops/sec ±2.47% (82 runs sampled)
  132 + JS PARSER: + simple string x 4,567,358 ops/sec ±1.97% (81 runs sampled)
  133 + JS PARSER BUF: + simple string x 5,433,901 ops/sec ±0.66% (93 runs sampled)
  134 +
  135 + HIREDIS: : integer x 2,332,946 ops/sec ±0.47% (93 runs sampled)
  136 + JS PARSER: : integer x 17,730,449 ops/sec ±0.73% (91 runs sampled)
  137 + JS PARSER STR: : integer x 12,942,037 ops/sec ±0.51% (92 runs sampled)
  138 +
  139 + HIREDIS: : big integer x 2,012,572 ops/sec ±0.33% (93 runs sampled)
  140 + JS PARSER: : big integer x 10,210,923 ops/sec ±0.94% (94 runs sampled)
  141 + JS PARSER STR: : big integer x 4,453,320 ops/sec ±0.52% (94 runs sampled)
  142 +
  143 + HIREDIS: * array x 44,479 ops/sec ±0.55% (94 runs sampled)
  144 + HIREDIS BUF: * array x 14,391 ops/sec ±1.04% (86 runs sampled)
  145 + JS PARSER: * array x 53,796 ops/sec ±2.08% (79 runs sampled)
  146 + JS PARSER BUF: * array x 72,428 ops/sec ±0.72% (93 runs sampled)
  147 +
  148 + HIREDIS: * big nested array x 217 ops/sec ±0.97% (83 runs sampled)
  149 + HIREDIS BUF: * big nested array x 255 ops/sec ±2.28% (77 runs sampled)
  150 + JS PARSER: * big nested array x 242 ops/sec ±1.10% (85 runs sampled)
  151 + JS PARSER BUF: * big nested array x 375 ops/sec ±1.21% (88 runs sampled)
  152 +
  153 + HIREDIS: - error x 78,821 ops/sec ±0.80% (93 runs sampled)
  154 + JS PARSER: - error x 143,382 ops/sec ±0.75% (92 runs sampled)
  155 +
  156 + Platform info:
  157 + Ubuntu 16.10
  158 + Node.js 7.4.0
  159 + Intel(R) Core(TM) i7-5600U CPU
  160 +
  161 +## License
  162 +
  163 +[MIT](./LICENSE)
  1 +## v.2.6.0 - 03 Apr, 2017
  2 +
  3 +Internals
  4 +
  5 +- Use Buffer.allocUnsafe instead of new Buffer() with modern Node.js versions
  6 +
  7 +## v.2.5.0 - 11 Mar, 2017
  8 +
  9 +Features
  10 +
  11 +- Added a `ParserError` class to differentiate them to ReplyErrors. The class is also exported
  12 +
  13 +Bugfixes
  14 +
  15 +- All errors now show their error message again next to the error name in the stack trace
  16 +- ParserErrors now show the offset and buffer attributes while being logged
  17 +
  18 +## v.2.4.1 - 05 Feb, 2017
  19 +
  20 +Bugfixes
  21 +
  22 +- Fixed minimal memory consumption overhead for chunked buffers
  23 +
  24 +## v.2.4.0 - 25 Jan, 2017
  25 +
  26 +Features
  27 +
  28 +- Added `reset` function to reset the parser to it's initial values
  29 +- Added `setReturnBuffers` function to reset the returnBuffers option (Only for the JSParser)
  30 +- Added `setStringNumbers` function to reset the stringNumbers option (Only for the JSParser)
  31 +- All Errors are now of sub classes of the new `RedisError` class. It is also exported.
  32 +- Improved bulk string chunked data handling performance
  33 +
  34 +Bugfixes
  35 +
  36 +- Parsing time for big nested arrays is now linear
  37 +
  38 +## v.2.3.0 - 25 Nov, 2016
  39 +
  40 +Features
  41 +
  42 +- Parsing time for big arrays (e.g. 4mb+) is now linear and works well for arbitrary array sizes
  43 +
  44 +This case is a magnitude faster than before
  45 +
  46 + OLD STR: * big array x 1.09 ops/sec ±2.15% (7 runs sampled)
  47 + OLD BUF: * big array x 1.23 ops/sec ±2.67% (8 runs sampled)
  48 +
  49 + NEW STR: * big array x 273 ops/sec ±2.09% (85 runs sampled)
  50 + NEW BUF: * big array x 259 ops/sec ±1.32% (85 runs sampled)
  51 + (~10mb array with 1000 entries)
  52 +
  53 +## v.2.2.0 - 18 Nov, 2016
  54 +
  55 +Features
  56 +
  57 +- Improve `stringNumbers` parsing performance by up to 100%
  58 +
  59 +Bugfixes
  60 +
  61 +- Do not unref the interval anymore due to issues with NodeJS
  62 +
  63 +## v.2.1.1 - 31 Oct, 2016
  64 +
  65 +Bugfixes
  66 +
  67 +- Remove erroneously added const to support Node.js 0.10
  68 +
  69 +## v.2.1.0 - 30 Oct, 2016
  70 +
  71 +Features
  72 +
  73 +- Improve parser errors by adding more detailed information to them
  74 +- Accept manipulated Object.prototypes
  75 +- Unref the interval if used
  76 +
  77 +## v.2.0.4 - 21 Jul, 2016
  78 +
  79 +Bugfixes
  80 +
  81 +- Fixed multi byte characters getting corrupted
  82 +
  83 +## v.2.0.3 - 17 Jun, 2016
  84 +
  85 +Bugfixes
  86 +
  87 +- Fixed parser not working with huge buffers (e.g. 300 MB)
  88 +
  89 +## v.2.0.2 - 08 Jun, 2016
  90 +
  91 +Bugfixes
  92 +
  93 +- Fixed parser with returnBuffers option returning corrupted data
  94 +
  95 +## v.2.0.1 - 04 Jun, 2016
  96 +
  97 +Bugfixes
  98 +
  99 +- Fixed multiple parsers working concurrently resulting in faulty data in some cases
  100 +
  101 +## v.2.0.0 - 29 May, 2016
  102 +
  103 +The javascript parser got completely rewritten by [Michael Diarmid](https://github.com/Salakar) and [Ruben Bridgewater](https://github.com/BridgeAR) and is now a lot faster than the hiredis parser.
  104 +Therefore the hiredis parser was deprecated and should only be used for testing purposes and benchmarking comparison.
  105 +
  106 +All Errors returned by the parser are from now on of class ReplyError
  107 +
  108 +Features
  109 +
  110 +- Improved performance by up to 15x as fast as before
  111 +- Improved options validation
  112 +- Added ReplyError Class
  113 +- Added parser benchmark
  114 +- Switched default parser from hiredis to JS, no matter if hiredis is installed or not
  115 +
  116 +Removed
  117 +
  118 +- Deprecated hiredis support
  119 +
  120 +## v.1.3.0 - 27 Mar, 2016
  121 +
  122 +Features
  123 +
  124 +- Added `auto` as parser name option to check what parser is available
  125 +- Non existing requested parsers falls back into auto mode instead of always choosing the JS parser
  126 +
  127 +## v.1.2.0 - 27 Mar, 2016
  128 +
  129 +Features
  130 +
  131 +- Added `stringNumbers` option to make sure all numbers are returned as string instead of a js number for precision
  132 +- The parser is from now on going to print warnings if a parser is explicitly requested that does not exist and gracefully chooses the JS parser
  133 +
  134 +## v.1.1.0 - 26 Jan, 2016
  135 +
  136 +Features
  137 +
  138 +- The parser is from now on going to reset itself on protocol errors
  1 +'use strict'
  2 +
  3 +module.exports = require('./lib/parser')
  4 +module.exports.ReplyError = require('./lib/replyError')
  5 +module.exports.RedisError = require('./lib/redisError')
  6 +module.exports.ParserError = require('./lib/redisError')
  1 +'use strict'
  2 +
  3 +var hiredis = require('hiredis')
  4 +var ReplyError = require('../lib/replyError')
  5 +var ParserError = require('../lib/parserError')
  6 +
  7 +/**
  8 + * Parse data
  9 + * @param parser
  10 + * @returns {*}
  11 + */
  12 +function parseData (parser, data) {
  13 + try {
  14 + return parser.reader.get()
  15 + } catch (err) {
  16 + // Protocol errors land here
  17 + // Reset the parser. Otherwise new commands can't be processed properly
  18 + parser.reader = new hiredis.Reader(parser.options)
  19 + parser.returnFatalError(new ParserError(err.message, JSON.stringify(data), -1))
  20 + }
  21 +}
  22 +
  23 +/**
  24 + * Hiredis Parser
  25 + * @param options
  26 + * @constructor
  27 + */
  28 +function HiredisReplyParser (options) {
  29 + this.returnError = options.returnError
  30 + this.returnFatalError = options.returnFatalError || options.returnError
  31 + this.returnReply = options.returnReply
  32 + this.name = 'hiredis'
  33 + this.options = {
  34 + return_buffers: !!options.returnBuffers
  35 + }
  36 + this.reader = new hiredis.Reader(this.options)
  37 +}
  38 +
  39 +HiredisReplyParser.prototype.execute = function (data) {
  40 + this.reader.feed(data)
  41 + var reply = parseData(this, data)
  42 +
  43 + while (reply !== undefined) {
  44 + if (reply && reply.name === 'Error') {
  45 + this.returnError(new ReplyError(reply.message))
  46 + } else {
  47 + this.returnReply(reply)
  48 + }
  49 + reply = parseData(this, data)
  50 + }
  51 +}
  52 +
  53 +/**
  54 + * Reset the parser values to the initial state
  55 + *
  56 + * @returns {undefined}
  57 + */
  58 +HiredisReplyParser.prototype.reset = function () {
  59 + this.reader = new hiredis.Reader(this.options)
  60 +}
  61 +
  62 +module.exports = HiredisReplyParser
  1 +'use strict'
  2 +
  3 +var StringDecoder = require('string_decoder').StringDecoder
  4 +var decoder = new StringDecoder()
  5 +var ReplyError = require('./replyError')
  6 +var ParserError = require('./parserError')
  7 +var bufferPool = bufferAlloc(32 * 1024)
  8 +var bufferOffset = 0
  9 +var interval = null
  10 +var counter = 0
  11 +var notDecreased = 0
  12 +var isModern = typeof Buffer.allocUnsafe === 'function'
  13 +
  14 +/**
  15 + * For backwards compatibility
  16 + * @param len
  17 + * @returns {Buffer}
  18 + */
  19 +
  20 +function bufferAlloc (len) {
  21 + return isModern ? Buffer.allocUnsafe(len) : new Buffer(len)
  22 +}
  23 +
  24 +/**
  25 + * Used for lengths and numbers only, faster perf on arrays / bulks
  26 + * @param parser
  27 + * @returns {*}
  28 + */
  29 +function parseSimpleNumbers (parser) {
  30 + var offset = parser.offset
  31 + var length = parser.buffer.length - 1
  32 + var number = 0
  33 + var sign = 1
  34 +
  35 + if (parser.buffer[offset] === 45) {
  36 + sign = -1
  37 + offset++
  38 + }
  39 +
  40 + while (offset < length) {
  41 + var c1 = parser.buffer[offset++]
  42 + if (c1 === 13) { // \r\n
  43 + parser.offset = offset + 1
  44 + return sign * number
  45 + }
  46 + number = (number * 10) + (c1 - 48)
  47 + }
  48 +}
  49 +
  50 +/**
  51 + * Used for integer numbers in case of the returnNumbers option
  52 + *
  53 + * The maximimum possible integer to use is: Math.floor(Number.MAX_SAFE_INTEGER / 10)
  54 + * Staying in a SMI Math.floor((Math.pow(2, 32) / 10) - 1) is even more efficient though
  55 + *
  56 + * @param parser
  57 + * @returns {*}
  58 + */
  59 +function parseStringNumbers (parser) {
  60 + var offset = parser.offset
  61 + var length = parser.buffer.length - 1
  62 + var number = 0
  63 + var res = ''
  64 +
  65 + if (parser.buffer[offset] === 45) {
  66 + res += '-'
  67 + offset++
  68 + }
  69 +
  70 + while (offset < length) {
  71 + var c1 = parser.buffer[offset++]
  72 + if (c1 === 13) { // \r\n
  73 + parser.offset = offset + 1
  74 + if (number !== 0) {
  75 + res += number
  76 + }
  77 + return res
  78 + } else if (number > 429496728) {
  79 + res += (number * 10) + (c1 - 48)
  80 + number = 0
  81 + } else if (c1 === 48 && number === 0) {
  82 + res += 0
  83 + } else {
  84 + number = (number * 10) + (c1 - 48)
  85 + }
  86 + }
  87 +}
  88 +
  89 +/**
  90 + * Returns a string or buffer of the provided offset start and
  91 + * end ranges. Checks `optionReturnBuffers`.
  92 + *
  93 + * If returnBuffers is active, all return values are returned as buffers besides numbers and errors
  94 + *
  95 + * @param parser
  96 + * @param start
  97 + * @param end
  98 + * @returns {*}
  99 + */
  100 +function convertBufferRange (parser, start, end) {
  101 + parser.offset = end + 2
  102 + if (parser.optionReturnBuffers === true) {
  103 + return parser.buffer.slice(start, end)
  104 + }
  105 +
  106 + return parser.buffer.toString('utf-8', start, end)
  107 +}
  108 +
  109 +/**
  110 + * Parse a '+' redis simple string response but forward the offsets
  111 + * onto convertBufferRange to generate a string.
  112 + * @param parser
  113 + * @returns {*}
  114 + */
  115 +function parseSimpleString (parser) {
  116 + var start = parser.offset
  117 + var offset = start
  118 + var buffer = parser.buffer
  119 + var length = buffer.length - 1
  120 +
  121 + while (offset < length) {
  122 + if (buffer[offset++] === 13) { // \r\n
  123 + return convertBufferRange(parser, start, offset - 1)
  124 + }
  125 + }
  126 +}
  127 +
  128 +/**
  129 + * Returns the string length via parseSimpleNumbers
  130 + * @param parser
  131 + * @returns {*}
  132 + */
  133 +function parseLength (parser) {
  134 + var string = parseSimpleNumbers(parser)
  135 + if (string !== undefined) {
  136 + return string
  137 + }
  138 +}
  139 +
  140 +/**
  141 + * Parse a ':' redis integer response
  142 + *
  143 + * If stringNumbers is activated the parser always returns numbers as string
  144 + * This is important for big numbers (number > Math.pow(2, 53)) as js numbers
  145 + * are 64bit floating point numbers with reduced precision
  146 + *
  147 + * @param parser
  148 + * @returns {*}
  149 + */
  150 +function parseInteger (parser) {
  151 + if (parser.optionStringNumbers) {
  152 + return parseStringNumbers(parser)
  153 + }
  154 + return parseSimpleNumbers(parser)
  155 +}
  156 +
  157 +/**
  158 + * Parse a '$' redis bulk string response
  159 + * @param parser
  160 + * @returns {*}
  161 + */
  162 +function parseBulkString (parser) {
  163 + var length = parseLength(parser)
  164 + if (length === undefined) {
  165 + return
  166 + }
  167 + if (length === -1) {
  168 + return null
  169 + }
  170 + var offsetEnd = parser.offset + length
  171 + if (offsetEnd + 2 > parser.buffer.length) {
  172 + parser.bigStrSize = offsetEnd + 2
  173 + parser.bigOffset = parser.offset
  174 + parser.totalChunkSize = parser.buffer.length
  175 + parser.bufferCache.push(parser.buffer)
  176 + return
  177 + }
  178 +
  179 + return convertBufferRange(parser, parser.offset, offsetEnd)
  180 +}
  181 +
  182 +/**
  183 + * Parse a '-' redis error response
  184 + * @param parser
  185 + * @returns {Error}
  186 + */
  187 +function parseError (parser) {
  188 + var string = parseSimpleString(parser)
  189 + if (string !== undefined) {
  190 + if (parser.optionReturnBuffers === true) {
  191 + string = string.toString()
  192 + }
  193 + return new ReplyError(string)
  194 + }
  195 +}
  196 +
  197 +/**
  198 + * Parsing error handler, resets parser buffer
  199 + * @param parser
  200 + * @param error
  201 + */
  202 +function handleError (parser, error) {
  203 + parser.buffer = null
  204 + parser.returnFatalError(error)
  205 +}
  206 +
  207 +/**
  208 + * Parse a '*' redis array response
  209 + * @param parser
  210 + * @returns {*}
  211 + */
  212 +function parseArray (parser) {
  213 + var length = parseLength(parser)
  214 + if (length === undefined) {
  215 + return
  216 + }
  217 + if (length === -1) {
  218 + return null
  219 + }
  220 + var responses = new Array(length)
  221 + return parseArrayElements(parser, responses, 0)
  222 +}
  223 +
  224 +/**
  225 + * Push a partly parsed array to the stack
  226 + *
  227 + * @param parser
  228 + * @param elem
  229 + * @param i
  230 + * @returns {undefined}
  231 + */
  232 +function pushArrayCache (parser, elem, pos) {
  233 + parser.arrayCache.push(elem)
  234 + parser.arrayPos.push(pos)
  235 +}
  236 +
  237 +/**
  238 + * Parse chunked redis array response
  239 + * @param parser
  240 + * @returns {*}
  241 + */
  242 +function parseArrayChunks (parser) {
  243 + var tmp = parser.arrayCache.pop()
  244 + var pos = parser.arrayPos.pop()
  245 + if (parser.arrayCache.length) {
  246 + var res = parseArrayChunks(parser)
  247 + if (!res) {
  248 + pushArrayCache(parser, tmp, pos)
  249 + return
  250 + }
  251 + tmp[pos++] = res
  252 + }
  253 + return parseArrayElements(parser, tmp, pos)
  254 +}
  255 +
  256 +/**
  257 + * Parse redis array response elements
  258 + * @param parser
  259 + * @param responses
  260 + * @param i
  261 + * @returns {*}
  262 + */
  263 +function parseArrayElements (parser, responses, i) {
  264 + var bufferLength = parser.buffer.length
  265 + while (i < responses.length) {
  266 + var offset = parser.offset
  267 + if (parser.offset >= bufferLength) {
  268 + pushArrayCache(parser, responses, i)
  269 + return
  270 + }
  271 + var response = parseType(parser, parser.buffer[parser.offset++])
  272 + if (response === undefined) {
  273 + if (!parser.arrayCache.length) {
  274 + parser.offset = offset
  275 + }
  276 + pushArrayCache(parser, responses, i)
  277 + return
  278 + }
  279 + responses[i] = response
  280 + i++
  281 + }
  282 +
  283 + return responses
  284 +}
  285 +
  286 +/**
  287 + * Called the appropriate parser for the specified type.
  288 + * @param parser
  289 + * @param type
  290 + * @returns {*}
  291 + */
  292 +function parseType (parser, type) {
  293 + switch (type) {
  294 + case 36: // $
  295 + return parseBulkString(parser)
  296 + case 58: // :
  297 + return parseInteger(parser)
  298 + case 43: // +
  299 + return parseSimpleString(parser)
  300 + case 42: // *
  301 + return parseArray(parser)
  302 + case 45: // -
  303 + return parseError(parser)
  304 + default:
  305 + return handleError(parser, new ParserError(
  306 + 'Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte',
  307 + JSON.stringify(parser.buffer),
  308 + parser.offset
  309 + ))
  310 + }
  311 +}
  312 +
  313 +// All allowed options including their typeof value
  314 +var optionTypes = {
  315 + returnError: 'function',
  316 + returnFatalError: 'function',
  317 + returnReply: 'function',
  318 + returnBuffers: 'boolean',
  319 + stringNumbers: 'boolean',
  320 + name: 'string'
  321 +}
  322 +
  323 +/**
  324 + * Javascript Redis Parser
  325 + * @param options
  326 + * @constructor
  327 + */
  328 +function JavascriptRedisParser (options) {
  329 + if (!(this instanceof JavascriptRedisParser)) {
  330 + return new JavascriptRedisParser(options)
  331 + }
  332 + if (!options || !options.returnError || !options.returnReply) {
  333 + throw new TypeError('Please provide all return functions while initiating the parser')
  334 + }
  335 + for (var key in options) {
  336 + // eslint-disable-next-line valid-typeof
  337 + if (optionTypes.hasOwnProperty(key) && typeof options[key] !== optionTypes[key]) {
  338 + throw new TypeError('The options argument contains the property "' + key + '" that is either unknown or of a wrong type')
  339 + }
  340 + }
  341 + if (options.name === 'hiredis') {
  342 + /* istanbul ignore next: hiredis is only supported for legacy usage */
  343 + try {
  344 + var Hiredis = require('./hiredis')
  345 + console.error(new TypeError('Using hiredis is discouraged. Please use the faster JS parser by removing the name option.').stack.replace('Error', 'Warning'))
  346 + return new Hiredis(options)
  347 + } catch (e) {
  348 + console.error(new TypeError('Hiredis is not installed. Please remove the `name` option. The (faster) JS parser is used instead.').stack.replace('Error', 'Warning'))
  349 + }
  350 + }
  351 + this.optionReturnBuffers = !!options.returnBuffers
  352 + this.optionStringNumbers = !!options.stringNumbers
  353 + this.returnError = options.returnError
  354 + this.returnFatalError = options.returnFatalError || options.returnError
  355 + this.returnReply = options.returnReply
  356 + this.name = 'javascript'
  357 + this.reset()
  358 +}
  359 +
  360 +/**
  361 + * Reset the parser values to the initial state
  362 + *
  363 + * @returns {undefined}
  364 + */
  365 +JavascriptRedisParser.prototype.reset = function () {
  366 + this.offset = 0
  367 + this.buffer = null
  368 + this.bigStrSize = 0
  369 + this.bigOffset = 0
  370 + this.totalChunkSize = 0
  371 + this.bufferCache = []
  372 + this.arrayCache = []
  373 + this.arrayPos = []
  374 +}
  375 +
  376 +/**
  377 + * Set the returnBuffers option
  378 + *
  379 + * @param returnBuffers
  380 + * @returns {undefined}
  381 + */
  382 +JavascriptRedisParser.prototype.setReturnBuffers = function (returnBuffers) {
  383 + if (typeof returnBuffers !== 'boolean') {
  384 + throw new TypeError('The returnBuffers argument has to be a boolean')
  385 + }
  386 + this.optionReturnBuffers = returnBuffers
  387 +}
  388 +
  389 +/**
  390 + * Set the stringNumbers option
  391 + *
  392 + * @param stringNumbers
  393 + * @returns {undefined}
  394 + */
  395 +JavascriptRedisParser.prototype.setStringNumbers = function (stringNumbers) {
  396 + if (typeof stringNumbers !== 'boolean') {
  397 + throw new TypeError('The stringNumbers argument has to be a boolean')
  398 + }
  399 + this.optionStringNumbers = stringNumbers
  400 +}
  401 +
  402 +/**
  403 + * Decrease the bufferPool size over time
  404 + * @returns {undefined}
  405 + */
  406 +function decreaseBufferPool () {
  407 + if (bufferPool.length > 50 * 1024) {
  408 + // Balance between increasing and decreasing the bufferPool
  409 + if (counter === 1 || notDecreased > counter * 2) {
  410 + // Decrease the bufferPool by 10% by removing the first 10% of the current pool
  411 + var sliceLength = Math.floor(bufferPool.length / 10)
  412 + if (bufferOffset <= sliceLength) {
  413 + bufferOffset = 0
  414 + } else {
  415 + bufferOffset -= sliceLength
  416 + }
  417 + bufferPool = bufferPool.slice(sliceLength, bufferPool.length)
  418 + } else {
  419 + notDecreased++
  420 + counter--
  421 + }
  422 + } else {
  423 + clearInterval(interval)
  424 + counter = 0
  425 + notDecreased = 0
  426 + interval = null
  427 + }
  428 +}
  429 +
  430 +/**
  431 + * Check if the requested size fits in the current bufferPool.
  432 + * If it does not, reset and increase the bufferPool accordingly.
  433 + *
  434 + * @param length
  435 + * @returns {undefined}
  436 + */
  437 +function resizeBuffer (length) {
  438 + if (bufferPool.length < length + bufferOffset) {
  439 + var multiplier = length > 1024 * 1024 * 75 ? 2 : 3
  440 + if (bufferOffset > 1024 * 1024 * 111) {
  441 + bufferOffset = 1024 * 1024 * 50
  442 + }
  443 + bufferPool = bufferAlloc(length * multiplier + bufferOffset)
  444 + bufferOffset = 0
  445 + counter++
  446 + if (interval === null) {
  447 + interval = setInterval(decreaseBufferPool, 50)
  448 + }
  449 + }
  450 +}
  451 +
  452 +/**
  453 + * Concat a bulk string containing multiple chunks
  454 + *
  455 + * Notes:
  456 + * 1) The first chunk might contain the whole bulk string including the \r
  457 + * 2) We are only safe to fully add up elements that are neither the first nor any of the last two elements
  458 + *
  459 + * @param parser
  460 + * @returns {String}
  461 + */
  462 +function concatBulkString (parser) {
  463 + var list = parser.bufferCache
  464 + var chunks = list.length
  465 + var offset = parser.bigStrSize - parser.totalChunkSize
  466 + parser.offset = offset
  467 + if (offset <= 2) {
  468 + if (chunks === 2) {
  469 + return list[0].toString('utf8', parser.bigOffset, list[0].length + offset - 2)
  470 + }
  471 + chunks--
  472 + offset = list[list.length - 2].length + offset
  473 + }
  474 + var res = decoder.write(list[0].slice(parser.bigOffset))
  475 + for (var i = 1; i < chunks - 1; i++) {
  476 + res += decoder.write(list[i])
  477 + }
  478 + res += decoder.end(list[i].slice(0, offset - 2))
  479 + return res
  480 +}
  481 +
  482 +/**
  483 + * Concat the collected chunks from parser.bufferCache.
  484 + *
  485 + * Increases the bufferPool size beforehand if necessary.
  486 + *
  487 + * @param parser
  488 + * @returns {Buffer}
  489 + */
  490 +function concatBulkBuffer (parser) {
  491 + var list = parser.bufferCache
  492 + var chunks = list.length
  493 + var length = parser.bigStrSize - parser.bigOffset - 2
  494 + var offset = parser.bigStrSize - parser.totalChunkSize
  495 + parser.offset = offset
  496 + if (offset <= 2) {
  497 + if (chunks === 2) {
  498 + return list[0].slice(parser.bigOffset, list[0].length + offset - 2)
  499 + }
  500 + chunks--
  501 + offset = list[list.length - 2].length + offset
  502 + }
  503 + resizeBuffer(length)
  504 + var start = bufferOffset
  505 + list[0].copy(bufferPool, start, parser.bigOffset, list[0].length)
  506 + bufferOffset += list[0].length - parser.bigOffset
  507 + for (var i = 1; i < chunks - 1; i++) {
  508 + list[i].copy(bufferPool, bufferOffset)
  509 + bufferOffset += list[i].length
  510 + }
  511 + list[i].copy(bufferPool, bufferOffset, 0, offset - 2)
  512 + bufferOffset += offset - 2
  513 + return bufferPool.slice(start, bufferOffset)
  514 +}
  515 +
  516 +/**
  517 + * Parse the redis buffer
  518 + * @param buffer
  519 + * @returns {undefined}
  520 + */
  521 +JavascriptRedisParser.prototype.execute = function execute (buffer) {
  522 + if (this.buffer === null) {
  523 + this.buffer = buffer
  524 + this.offset = 0
  525 + } else if (this.bigStrSize === 0) {
  526 + var oldLength = this.buffer.length
  527 + var remainingLength = oldLength - this.offset
  528 + var newBuffer = bufferAlloc(remainingLength + buffer.length)
  529 + this.buffer.copy(newBuffer, 0, this.offset, oldLength)
  530 + buffer.copy(newBuffer, remainingLength, 0, buffer.length)
  531 + this.buffer = newBuffer
  532 + this.offset = 0
  533 + if (this.arrayCache.length) {
  534 + var arr = parseArrayChunks(this)
  535 + if (!arr) {
  536 + return
  537 + }
  538 + this.returnReply(arr)
  539 + }
  540 + } else if (this.totalChunkSize + buffer.length >= this.bigStrSize) {
  541 + this.bufferCache.push(buffer)
  542 + var tmp = this.optionReturnBuffers ? concatBulkBuffer(this) : concatBulkString(this)
  543 + this.bigStrSize = 0
  544 + this.bufferCache = []
  545 + this.buffer = buffer
  546 + if (this.arrayCache.length) {
  547 + this.arrayCache[0][this.arrayPos[0]++] = tmp
  548 + tmp = parseArrayChunks(this)
  549 + if (!tmp) {
  550 + return
  551 + }
  552 + }
  553 + this.returnReply(tmp)
  554 + } else {
  555 + this.bufferCache.push(buffer)
  556 + this.totalChunkSize += buffer.length
  557 + return
  558 + }
  559 +
  560 + while (this.offset < this.buffer.length) {
  561 + var offset = this.offset
  562 + var type = this.buffer[this.offset++]
  563 + var response = parseType(this, type)
  564 + if (response === undefined) {
  565 + if (!this.arrayCache.length) {
  566 + this.offset = offset
  567 + }
  568 + return
  569 + }
  570 +
  571 + if (type === 45) {
  572 + this.returnError(response)
  573 + } else {
  574 + this.returnReply(response)
  575 + }
  576 + }
  577 +
  578 + this.buffer = null
  579 +}
  580 +
  581 +module.exports = JavascriptRedisParser
  1 +'use strict'
  2 +
  3 +var util = require('util')
  4 +var assert = require('assert')
  5 +var RedisError = require('./redisError')
  6 +var ADD_STACKTRACE = false
  7 +
  8 +function ParserError (message, buffer, offset) {
  9 + assert(buffer)
  10 + assert.strictEqual(typeof offset, 'number')
  11 + RedisError.call(this, message, ADD_STACKTRACE)
  12 + this.offset = offset
  13 + this.buffer = buffer
  14 + Error.captureStackTrace(this, ParserError)
  15 +}
  16 +
  17 +util.inherits(ParserError, RedisError)
  18 +
  19 +Object.defineProperty(ParserError.prototype, 'name', {
  20 + value: 'ParserError',
  21 + configurable: true,
  22 + writable: true
  23 +})
  24 +
  25 +module.exports = ParserError
  1 +'use strict'
  2 +
  3 +var util = require('util')
  4 +
  5 +function RedisError (message, stack) {
  6 + Object.defineProperty(this, 'message', {
  7 + value: message || '',
  8 + configurable: true,
  9 + writable: true
  10 + })
  11 + if (stack || stack === undefined) {
  12 + Error.captureStackTrace(this, RedisError)
  13 + }
  14 +}
  15 +
  16 +util.inherits(RedisError, Error)
  17 +
  18 +Object.defineProperty(RedisError.prototype, 'name', {
  19 + value: 'RedisError',
  20 + configurable: true,
  21 + writable: true
  22 +})
  23 +
  24 +module.exports = RedisError
  1 +'use strict'
  2 +
  3 +var util = require('util')
  4 +var RedisError = require('./redisError')
  5 +var ADD_STACKTRACE = false
  6 +
  7 +function ReplyError (message) {
  8 + var tmp = Error.stackTraceLimit
  9 + Error.stackTraceLimit = 2
  10 + RedisError.call(this, message, ADD_STACKTRACE)
  11 + Error.captureStackTrace(this, ReplyError)
  12 + Error.stackTraceLimit = tmp
  13 +}
  14 +
  15 +util.inherits(ReplyError, RedisError)
  16 +
  17 +Object.defineProperty(ReplyError.prototype, 'name', {
  18 + value: 'ReplyError',
  19 + configurable: true,
  20 + writable: true
  21 +})
  22 +
  23 +module.exports = ReplyError
  1 +{
  2 + "_args": [
  3 + [
  4 + {
  5 + "raw": "redis-parser@^2.6.0",
  6 + "scope": null,
  7 + "escapedName": "redis-parser",
  8 + "name": "redis-parser",
  9 + "rawSpec": "^2.6.0",
  10 + "spec": ">=2.6.0 <3.0.0",
  11 + "type": "range"
  12 + },
  13 + "/Users/fzy/project/koa2_Sequelize_project/node_modules/redis"
  14 + ]
  15 + ],
  16 + "_from": "redis-parser@>=2.6.0 <3.0.0",
  17 + "_id": "redis-parser@2.6.0",
  18 + "_inCache": true,
  19 + "_location": "/redis-parser",
  20 + "_nodeVersion": "7.7.3",
  21 + "_npmOperationalInternal": {
  22 + "host": "packages-12-west.internal.npmjs.com",
  23 + "tmp": "tmp/redis-parser-2.6.0.tgz_1491263124772_0.7531374620739371"
  24 + },
  25 + "_npmUser": {
  26 + "name": "bridgear",
  27 + "email": "ruben@bridgewater.de"
  28 + },
  29 + "_npmVersion": "4.1.2",
  30 + "_phantomChildren": {},
  31 + "_requested": {
  32 + "raw": "redis-parser@^2.6.0",
  33 + "scope": null,
  34 + "escapedName": "redis-parser",
  35 + "name": "redis-parser",
  36 + "rawSpec": "^2.6.0",
  37 + "spec": ">=2.6.0 <3.0.0",
  38 + "type": "range"
  39 + },
  40 + "_requiredBy": [
  41 + "/redis"
  42 + ],
  43 + "_resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz",
  44 + "_shasum": "52ed09dacac108f1a631c07e9b69941e7a19504b",
  45 + "_shrinkwrap": null,
  46 + "_spec": "redis-parser@^2.6.0",
  47 + "_where": "/Users/fzy/project/koa2_Sequelize_project/node_modules/redis",
  48 + "author": {
  49 + "name": "Ruben Bridgewater"
  50 + },
  51 + "bugs": {
  52 + "url": "https://github.com/NodeRedis/node-redis-parser/issues"
  53 + },
  54 + "dependencies": {},
  55 + "description": "Javascript Redis protocol (RESP) parser",
  56 + "devDependencies": {
  57 + "benchmark": "^2.1.0",
  58 + "codeclimate-test-reporter": "^0.4.0",
  59 + "hiredis": "^0.5.0",
  60 + "intercept-stdout": "^0.1.2",
  61 + "istanbul": "^0.4.0",
  62 + "mocha": "^3.1.2",
  63 + "standard": "^9.0.0"
  64 + },
  65 + "directories": {
  66 + "test": "test",
  67 + "lib": "lib"
  68 + },
  69 + "dist": {
  70 + "shasum": "52ed09dacac108f1a631c07e9b69941e7a19504b",
  71 + "tarball": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz"
  72 + },
  73 + "engines": {
  74 + "node": ">=0.10.0"
  75 + },
  76 + "gitHead": "eea04cad0c4f53fd1e9f7079b5f4ededf50f5945",
  77 + "homepage": "https://github.com/NodeRedis/node-redis-parser#readme",
  78 + "keywords": [
  79 + "redis",
  80 + "protocol",
  81 + "parser",
  82 + "database",
  83 + "javascript",
  84 + "node",
  85 + "nodejs",
  86 + "resp",
  87 + "hiredis"
  88 + ],
  89 + "license": "MIT",
  90 + "main": "index.js",
  91 + "maintainers": [
  92 + {
  93 + "name": "bridgear",
  94 + "email": "ruben@bridgewater.de"
  95 + }
  96 + ],
  97 + "name": "redis-parser",
  98 + "optionalDependencies": {},
  99 + "readme": "[![Build Status](https://travis-ci.org/NodeRedis/node-redis-parser.png?branch=master)](https://travis-ci.org/NodeRedis/node-redis-parser)\n[![Test Coverage](https://codeclimate.com/github/NodeRedis/node-redis-parser/badges/coverage.svg)](https://codeclimate.com/github/NodeRedis/node-redis-parser/coverage)\n[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)\n\n# redis-parser\n\nA high performance javascript redis parser built for [node_redis](https://github.com/NodeRedis/node_redis) and [ioredis](https://github.com/luin/ioredis). Parses all [RESP](http://redis.io/topics/protocol) data.\n\n## Install\n\nInstall with [NPM](https://npmjs.org/):\n\n npm install redis-parser\n\n## Usage\n\n```js\nvar Parser = require('redis-parser');\n\nvar myParser = new Parser(options);\n```\n\n### Options\n\n* `returnReply`: *function*; mandatory\n* `returnError`: *function*; mandatory\n* `returnFatalError`: *function*; optional, defaults to the returnError function\n* `returnBuffers`: *boolean*; optional, defaults to false\n* `stringNumbers`: *boolean*; optional, defaults to false\n\n### Functions\n\n* `reset()`: reset the parser to it's initial state\n* `setReturnBuffers(boolean)`: (JSParser only) set the returnBuffers option on/off without resetting the parser\n* `setStringNumbers(boolean)`: (JSParser only) set the stringNumbers option on/off without resetting the parser\n\n### Error classes\n\n* `RedisError` sub class of Error\n* `ReplyError` sub class of RedisError\n* `ParserError` sub class of RedisError\n\nAll Redis errors will be returned as `ReplyErrors` while a parser error is returned as `ParserError`. \nAll error classes are exported by the parser.\n\n### Example\n\n```js\nvar Parser = require(\"redis-parser\");\n\nfunction Library () {}\n\nLibrary.prototype.returnReply = function (reply) { ... }\nLibrary.prototype.returnError = function (err) { ... }\nLibrary.prototype.returnFatalError = function (err) { ... }\n\nvar lib = new Library();\n\nvar parser = new Parser({\n returnReply: function(reply) {\n lib.returnReply(reply);\n },\n returnError: function(err) {\n lib.returnError(err);\n },\n returnFatalError: function (err) {\n lib.returnFatalError(err);\n }\n});\n\nLibrary.prototype.streamHandler = function () {\n this.stream.on('data', function (buffer) {\n // Here the data (e.g. `new Buffer('$5\\r\\nHello\\r\\n'`)) is passed to the parser and the result is passed to either function depending on the provided data.\n parser.execute(buffer);\n });\n};\n```\nYou do not have to use the returnFatalError function. Fatal errors will be returned in the normal error function in that case.\n\nAnd if you want to return buffers instead of strings, you can do this by adding the `returnBuffers` option.\n\nIf you handle with big numbers that are to large for JS (Number.MAX_SAFE_INTEGER === 2^53 - 16) please use the `stringNumbers` option. That way all numbers are going to be returned as String and you can handle them safely.\n\n```js\n// Same functions as in the first example\n\nvar parser = new Parser({\n returnReply: function(reply) {\n lib.returnReply(reply);\n },\n returnError: function(err) {\n lib.returnError(err);\n },\n returnBuffers: true, // All strings are returned as Buffer e.g. <Buffer 48 65 6c 6c 6f>\n stringNumbers: true // All numbers are returned as String\n});\n\n// The streamHandler as above\n```\n\n## Protocol errors\n\nTo handle protocol errors (this is very unlikely to happen) gracefully you should add the returnFatalError option, reject any still running command (they might have been processed properly but the reply is just wrong), destroy the socket and reconnect. Note that while doing this no new command may be added, so all new commands have to be buffered in the meantime, otherwise a chunk might still contain partial data of a following command that was already processed properly but answered in the same chunk as the command that resulted in the protocol error.\n\n## Contribute\n\nThe parser is highly optimized but there may still be further optimizations possible.\n\n npm install\n npm test\n npm run benchmark\n\nCurrently the benchmark compares the performance against the hiredis parser:\n\n HIREDIS: $ multiple chunks in a bulk string x 859,880 ops/sec ±1.22% (82 runs sampled)\n HIREDIS BUF: $ multiple chunks in a bulk string x 608,869 ops/sec ±1.72% (85 runs sampled)\n JS PARSER: $ multiple chunks in a bulk string x 910,590 ops/sec ±0.87% (89 runs sampled)\n JS PARSER BUF: $ multiple chunks in a bulk string x 1,299,507 ops/sec ±2.18% (84 runs sampled)\n\n HIREDIS: + multiple chunks in a string x 1,787,203 ops/sec ±0.58% (96 runs sampled)\n HIREDIS BUF: + multiple chunks in a string x 943,584 ops/sec ±1.62% (87 runs sampled)\n JS PARSER: + multiple chunks in a string x 2,008,264 ops/sec ±1.01% (91 runs sampled)\n JS PARSER BUF: + multiple chunks in a string x 2,045,546 ops/sec ±0.78% (91 runs sampled)\n\n HIREDIS: $ 4mb bulk string x 310 ops/sec ±1.58% (75 runs sampled)\n HIREDIS BUF: $ 4mb bulk string x 471 ops/sec ±2.28% (78 runs sampled)\n JS PARSER: $ 4mb bulk string x 747 ops/sec ±2.43% (85 runs sampled)\n JS PARSER BUF: $ 4mb bulk string x 846 ops/sec ±5.52% (72 runs sampled)\n\n HIREDIS: + simple string x 2,324,866 ops/sec ±1.61% (90 runs sampled)\n HIREDIS BUF: + simple string x 1,085,823 ops/sec ±2.47% (82 runs sampled)\n JS PARSER: + simple string x 4,567,358 ops/sec ±1.97% (81 runs sampled)\n JS PARSER BUF: + simple string x 5,433,901 ops/sec ±0.66% (93 runs sampled)\n\n HIREDIS: : integer x 2,332,946 ops/sec ±0.47% (93 runs sampled)\n JS PARSER: : integer x 17,730,449 ops/sec ±0.73% (91 runs sampled)\n JS PARSER STR: : integer x 12,942,037 ops/sec ±0.51% (92 runs sampled)\n\n HIREDIS: : big integer x 2,012,572 ops/sec ±0.33% (93 runs sampled)\n JS PARSER: : big integer x 10,210,923 ops/sec ±0.94% (94 runs sampled)\n JS PARSER STR: : big integer x 4,453,320 ops/sec ±0.52% (94 runs sampled)\n\n HIREDIS: * array x 44,479 ops/sec ±0.55% (94 runs sampled)\n HIREDIS BUF: * array x 14,391 ops/sec ±1.04% (86 runs sampled)\n JS PARSER: * array x 53,796 ops/sec ±2.08% (79 runs sampled)\n JS PARSER BUF: * array x 72,428 ops/sec ±0.72% (93 runs sampled)\n\n HIREDIS: * big nested array x 217 ops/sec ±0.97% (83 runs sampled)\n HIREDIS BUF: * big nested array x 255 ops/sec ±2.28% (77 runs sampled)\n JS PARSER: * big nested array x 242 ops/sec ±1.10% (85 runs sampled)\n JS PARSER BUF: * big nested array x 375 ops/sec ±1.21% (88 runs sampled)\n\n HIREDIS: - error x 78,821 ops/sec ±0.80% (93 runs sampled)\n JS PARSER: - error x 143,382 ops/sec ±0.75% (92 runs sampled)\n\n Platform info:\n Ubuntu 16.10\n Node.js 7.4.0\n Intel(R) Core(TM) i7-5600U CPU\n\n## License\n\n[MIT](./LICENSE)\n",
  100 + "readmeFilename": "README.md",
  101 + "repository": {
  102 + "type": "git",
  103 + "url": "git+https://github.com/NodeRedis/node-redis-parser.git"
  104 + },
  105 + "scripts": {
  106 + "benchmark": "node ./benchmark",
  107 + "coverage": "node ./node_modules/istanbul/lib/cli.js cover --preserve-comments ./node_modules/mocha/bin/_mocha -- -R spec",
  108 + "coverage:check": "node ./node_modules/istanbul/lib/cli.js check-coverage --branch 100 --statement 100",
  109 + "lint": "standard --fix",
  110 + "posttest": "npm run lint && npm run coverage:check",
  111 + "test": "npm run coverage"
  112 + },
  113 + "version": "2.6.0"
  114 +}
  1 +node_modules/**
  2 +coverage/**
  3 +**.md
  4 +**.log
  1 +env:
  2 + node: true
  3 + es6: false
  4 +
  5 +rules:
  6 + # Possible Errors
  7 + # http://eslint.org/docs/rules/#possible-errors
  8 + comma-dangle: [2, "only-multiline"]
  9 + no-constant-condition: 2
  10 + no-control-regex: 2
  11 + no-debugger: 2
  12 + no-dupe-args: 2
  13 + no-dupe-keys: 2
  14 + no-duplicate-case: 2
  15 + no-empty: 2
  16 + no-empty-character-class: 2
  17 + no-ex-assign: 2
  18 + no-extra-boolean-cast : 2
  19 + no-extra-parens: [2, "functions"]
  20 + no-extra-semi: 2
  21 + no-func-assign: 2
  22 + no-invalid-regexp: 2
  23 + no-irregular-whitespace: 2
  24 + no-negated-in-lhs: 2
  25 + no-obj-calls: 2
  26 + no-regex-spaces: 2
  27 + no-sparse-arrays: 2
  28 + no-inner-declarations: 2
  29 + no-unexpected-multiline: 2
  30 + no-unreachable: 2
  31 + use-isnan: 2
  32 + valid-typeof: 2
  33 +
  34 + # Best Practices
  35 + # http://eslint.org/docs/rules/#best-practices
  36 + array-callback-return: 2
  37 + block-scoped-var: 2
  38 + dot-notation: 2
  39 + eqeqeq: 2
  40 + no-else-return: 2
  41 + no-extend-native: 2
  42 + no-floating-decimal: 2
  43 + no-extra-bind: 2
  44 + no-fallthrough: 2
  45 + no-labels: 2
  46 + no-lone-blocks: 2
  47 + no-loop-func: 2
  48 + no-multi-spaces: 2
  49 + no-multi-str: 2
  50 + no-native-reassign: 2
  51 + no-new-wrappers: 2
  52 + no-octal: 2
  53 + no-proto: 2
  54 + no-redeclare: 2
  55 + no-return-assign: 2
  56 + no-self-assign: 2
  57 + no-self-compare: 2
  58 + no-sequences: 2
  59 + no-throw-literal: 2
  60 + no-useless-call: 2
  61 + no-useless-concat: 2
  62 + no-useless-escape: 2
  63 + no-void: 2
  64 + no-unmodified-loop-condition: 2
  65 + yoda: 2
  66 +
  67 + # Strict Mode
  68 + # http://eslint.org/docs/rules/#strict-mode
  69 + strict: [2, "global"]
  70 +
  71 + # Variables
  72 + # http://eslint.org/docs/rules/#variables
  73 + no-delete-var: 2
  74 + no-shadow-restricted-names: 2
  75 + no-undef: 2
  76 + no-unused-vars: [2, {"args": "none"}]
  77 +
  78 + # http://eslint.org/docs/rules/#nodejs-and-commonjs
  79 + no-mixed-requires: 2
  80 + no-new-require: 2
  81 + no-path-concat: 2
  82 +
  83 + # Stylistic Issues
  84 + # http://eslint.org/docs/rules/#stylistic-issues
  85 + comma-spacing: 2
  86 + eol-last: 2
  87 + indent: [2, 4, {SwitchCase: 2}]
  88 + keyword-spacing: 2
  89 + max-len: [2, 200, 2]
  90 + new-parens: 2
  91 + no-mixed-spaces-and-tabs: 2
  92 + no-multiple-empty-lines: [2, {max: 2}]
  93 + no-trailing-spaces: 2
  94 + quotes: [2, "single", "avoid-escape"]
  95 + semi: 2
  96 + space-before-blocks: [2, "always"]
  97 + space-before-function-paren: [2, "always"]
  98 + space-in-parens: [2, "never"]
  99 + space-infix-ops: 2
  100 + space-unary-ops: 2
  101 +
  102 +globals:
  103 + it: true
  104 + describe: true
  105 + before: true
  106 + after: true
  107 + beforeEach: true
  108 + afterEach: true
  1 +_Thanks for wanting to report an issue you've found in node_redis. Please delete
  2 +this text and fill in the template below. Please note that the issue tracker is only
  3 +for bug reports or feature requests. If you have a question, please ask that on [gitter].
  4 +If unsure about something, just do as best as you're able._
  5 +
  6 +_Note that it will be much easier to fix the issue if a test case that reproduces
  7 +the problem is provided. It is of course not always possible to reduce your code
  8 +to a small test case, but it's highly appreciated to have as much data as possible.
  9 +Thank you!_
  10 +
  11 +* **Version**: What node_redis and what redis version is the issue happening on?
  12 +* **Platform**: What platform / version? (For example Node.js 0.10 or Node.js 5.7.0 on Windows 7 / Ubuntu 15.10 / Azure)
  13 +* **Description**: Description of your issue, stack traces from errors and code that reproduces the issue
  14 +
  15 +[gitter]: https://gitter.im/NodeRedis/node_redis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
  1 +### Pull Request check-list
  2 +
  3 +_Please make sure to review and check all of these items:_
  4 +
  5 +- [ ] Does `npm test` pass with this change (including linting)?
  6 +- [ ] Is the new or changed code fully tested?
  7 +- [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?
  8 +
  9 +_NOTE: these things are not required to open a PR and can be done
  10 +afterwards / while the PR is open._
  11 +
  12 +### Description of change
  13 +
  14 +_Please provide a description of the change here._
  1 +examples/
  2 +benchmarks/
  3 +test/
  4 +.nyc_output/
  5 +coverage/
  6 +.tern-port
  7 +*.log
  8 +*.rdb
  9 +*.out
  10 +*.yml
  1 +LICENSE - "MIT License"
  2 +
  3 +Copyright (c) 2016 by NodeRedis
  4 +
  5 +Permission is hereby granted, free of charge, to any person
  6 +obtaining a copy of this software and associated documentation
  7 +files (the "Software"), to deal in the Software without
  8 +restriction, including without limitation the rights to use,
  9 +copy, modify, merge, publish, distribute, sublicense, and/or sell
  10 +copies of the Software, and to permit persons to whom the
  11 +Software is furnished to do so, subject to the following
  12 +conditions:
  13 +
  14 +The above copyright notice and this permission notice shall be
  15 +included in all copies or substantial portions of the Software.
  16 +
  17 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  19 +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  21 +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  22 +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  23 +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  24 +OTHER DEALINGS IN THE SOFTWARE.
  1 +redis - a node.js redis client
  2 +===========================
  3 +
  4 +[![Build Status](https://travis-ci.org/NodeRedis/node_redis.svg?branch=master)](https://travis-ci.org/NodeRedis/node_redis)
  5 +[![Coverage Status](https://coveralls.io/repos/NodeRedis/node_redis/badge.svg?branch=)](https://coveralls.io/r/NodeRedis/node_redis?branch=)
  6 +[![Windows Tests](https://img.shields.io/appveyor/ci/BridgeAR/node-redis/master.svg?label=Windows%20Tests)](https://ci.appveyor.com/project/BridgeAR/node-redis/branch/master)
  7 +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/NodeRedis/node_redis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
  8 +
  9 +This is a complete and feature rich Redis client for node.js. __It supports all
  10 +Redis commands__ and focuses on high performance.
  11 +
  12 +Install with:
  13 +
  14 + npm install redis
  15 +
  16 +## Usage Example
  17 +
  18 +```js
  19 +var redis = require("redis"),
  20 + client = redis.createClient();
  21 +
  22 +// if you'd like to select database 3, instead of 0 (default), call
  23 +// client.select(3, function() { /* ... */ });
  24 +
  25 +client.on("error", function (err) {
  26 + console.log("Error " + err);
  27 +});
  28 +
  29 +client.set("string key", "string val", redis.print);
  30 +client.hset("hash key", "hashtest 1", "some value", redis.print);
  31 +client.hset(["hash key", "hashtest 2", "some other value"], redis.print);
  32 +client.hkeys("hash key", function (err, replies) {
  33 + console.log(replies.length + " replies:");
  34 + replies.forEach(function (reply, i) {
  35 + console.log(" " + i + ": " + reply);
  36 + });
  37 + client.quit();
  38 +});
  39 +```
  40 +
  41 +This will display:
  42 +
  43 + mjr:~/work/node_redis (master)$ node example.js
  44 + Reply: OK
  45 + Reply: 0
  46 + Reply: 0
  47 + 2 replies:
  48 + 0: hashtest 1
  49 + 1: hashtest 2
  50 + mjr:~/work/node_redis (master)$
  51 +
  52 +Note that the API is entirely asynchronous. To get data back from the server,
  53 +you'll need to use a callback. From v.2.6 on the API supports camelCase and
  54 +snake_case and all options / variables / events etc. can be used either way. It
  55 +is recommended to use camelCase as this is the default for the Node.js
  56 +landscape.
  57 +
  58 +### Promises
  59 +
  60 +You can also use node_redis with promises by promisifying node_redis with
  61 +[bluebird](https://github.com/petkaantonov/bluebird) as in:
  62 +
  63 +```js
  64 +var redis = require('redis');
  65 +bluebird.promisifyAll(redis.RedisClient.prototype);
  66 +bluebird.promisifyAll(redis.Multi.prototype);
  67 +```
  68 +
  69 +It'll add a *Async* to all node_redis functions (e.g. return client.getAsync().then())
  70 +
  71 +```js
  72 +// We expect a value 'foo': 'bar' to be present
  73 +// So instead of writing client.get('foo', cb); you have to write:
  74 +return client.getAsync('foo').then(function(res) {
  75 + console.log(res); // => 'bar'
  76 +});
  77 +
  78 +// Using multi with promises looks like:
  79 +
  80 +return client.multi().get('foo').execAsync().then(function(res) {
  81 + console.log(res); // => 'bar'
  82 +});
  83 +```
  84 +
  85 +### Sending Commands
  86 +
  87 +Each Redis command is exposed as a function on the `client` object.
  88 +All functions take either an `args` Array plus optional `callback` Function or
  89 +a variable number of individual arguments followed by an optional callback.
  90 +Examples:
  91 +
  92 +```js
  93 +client.hmset(["key", "test keys 1", "test val 1", "test keys 2", "test val 2"], function (err, res) {});
  94 +// Works the same as
  95 +client.hmset("key", ["test keys 1", "test val 1", "test keys 2", "test val 2"], function (err, res) {});
  96 +// Or
  97 +client.hmset("key", "test keys 1", "test val 1", "test keys 2", "test val 2", function (err, res) {});
  98 +```
  99 +
  100 +Note that in either form the `callback` is optional:
  101 +
  102 +```js
  103 +client.set("some key", "some val");
  104 +client.set(["some other key", "some val"]);
  105 +```
  106 +
  107 +If the key is missing, reply will be null. Only if the [Redis Command
  108 +Reference](http://redis.io/commands) states something else it will not be null.
  109 +
  110 +```js
  111 +client.get("missingkey", function(err, reply) {
  112 + // reply is null when the key is missing
  113 + console.log(reply);
  114 +});
  115 +```
  116 +
  117 +For a list of Redis commands, see [Redis Command Reference](http://redis.io/commands)
  118 +
  119 +Minimal parsing is done on the replies. Commands that return a integer return
  120 +JavaScript Numbers, arrays return JavaScript Array. `HGETALL` returns an Object
  121 +keyed by the hash keys. All strings will either be returned as string or as
  122 +buffer depending on your setting. Please be aware that sending null, undefined
  123 +and Boolean values will result in the value coerced to a string!
  124 +
  125 +# Redis Commands
  126 +
  127 +This library is a 1 to 1 mapping to [Redis commands](https://redis.io/commands).
  128 +It is not a cache library so please refer to Redis commands page for full usage
  129 +details.
  130 +
  131 +Example setting key to auto expire using [SET command](https://redis.io/commands/set)
  132 +
  133 +```js
  134 +// this key will expire after 10 seconds
  135 +client.set('key', 'value!', 'EX', 10);
  136 +```
  137 +
  138 +# API
  139 +
  140 +## Connection and other Events
  141 +
  142 +`client` will emit some events about the state of the connection to the Redis server.
  143 +
  144 +### "ready"
  145 +
  146 +`client` will emit `ready` once a connection is established. Commands issued
  147 +before the `ready` event are queued, then replayed just before this event is
  148 +emitted.
  149 +
  150 +### "connect"
  151 +
  152 +`client` will emit `connect` as soon as the stream is connected to the server.
  153 +
  154 +### "reconnecting"
  155 +
  156 +`client` will emit `reconnecting` when trying to reconnect to the Redis server
  157 +after losing the connection. Listeners are passed an object containing `delay`
  158 +(in ms) and `attempt` (the attempt #) attributes.
  159 +
  160 +### "error"
  161 +
  162 +`client` will emit `error` when encountering an error connecting to the Redis
  163 +server or when any other in node_redis occurs. If you use a command without
  164 +callback and encounter a ReplyError it is going to be emitted to the error
  165 +listener.
  166 +
  167 +So please attach the error listener to node_redis.
  168 +
  169 +### "end"
  170 +
  171 +`client` will emit `end` when an established Redis server connection has closed.
  172 +
  173 +### "drain" (deprecated)
  174 +
  175 +`client` will emit `drain` when the TCP connection to the Redis server has been
  176 +buffering, but is now writable. This event can be used to stream commands in to
  177 +Redis and adapt to backpressure.
  178 +
  179 +If the stream is buffering `client.should_buffer` is set to true. Otherwise the
  180 +variable is always set to false. That way you can decide when to reduce your
  181 +send rate and resume sending commands when you get `drain`.
  182 +
  183 +You can also check the return value of each command as it will also return the
  184 +backpressure indicator (deprecated). If false is returned the stream had to
  185 +buffer.
  186 +
  187 +### "warning"
  188 +
  189 +`client` will emit `warning` when password was set but none is needed and if a
  190 +deprecated option / function / similar is used.
  191 +
  192 +### "idle" (deprecated)
  193 +
  194 +`client` will emit `idle` when there are no outstanding commands that are
  195 +awaiting a response.
  196 +
  197 +## redis.createClient()
  198 +If you have `redis-server` running on the same machine as node, then the
  199 +defaults for port and host are probably fine and you don't need to supply any
  200 +arguments. `createClient()` returns a `RedisClient` object. Otherwise,
  201 +`createClient()` accepts these arguments:
  202 +
  203 +* `redis.createClient([options])`
  204 +* `redis.createClient(unix_socket[, options])`
  205 +* `redis.createClient(redis_url[, options])`
  206 +* `redis.createClient(port[, host][, options])`
  207 +
  208 +__Tip:__ If the Redis server runs on the same machine as the client consider
  209 +using unix sockets if possible to increase throughput.
  210 +
  211 +#### `options` object properties
  212 +| Property | Default | Description |
  213 +|-----------|-----------|-------------|
  214 +| host | 127.0.0.1 | IP address of the Redis server |
  215 +| port | 6379 | Port of the Redis server |
  216 +| path | null | The UNIX socket string of the Redis server |
  217 +| url | null | The URL of the Redis server. Format: `[redis:]//[[user][:password@]][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]` (More info avaliable at [IANA](http://www.iana.org/assignments/uri-schemes/prov/redis)). |
  218 +| parser | javascript | __Deprecated__ Use either the built-in JS parser [`javascript`]() or the native [`hiredis`]() parser. __Note__ `node_redis` < 2.6 uses hiredis as default if installed. This changed in v.2.6.0. |
  219 +| string_numbers | null | Set to `true`, `node_redis` will return Redis number values as Strings instead of javascript Numbers. Useful if you need to handle big numbers (above `Number.MAX_SAFE_INTEGER === 2^53`). Hiredis is incapable of this behavior, so setting this option to `true` will result in the built-in javascript parser being used no matter the value of the `parser` option. |
  220 +| return_buffers | false | If set to `true`, then all replies will be sent to callbacks as Buffers instead of Strings. |
  221 +| detect_buffers | false | If set to `true`, then replies will be sent to callbacks as Buffers. This option lets you switch between Buffers and Strings on a per-command basis, whereas `return_buffers` applies to every command on a client. __Note__: This doesn't work properly with the pubsub mode. A subscriber has to either always return Strings or Buffers. |
  222 +| socket_keepalive | true | If set to `true`, the keep-alive functionality is enabled on the underlying socket. |
  223 +| no_ready_check | false | When a connection is established to the Redis server, the server might still be loading the database from disk. While loading, the server will not respond to any commands. To work around this, `node_redis` has a "ready check" which sends the `INFO` command to the server. The response from the `INFO` command indicates whether the server is ready for more commands. When ready, `node_redis` emits a `ready` event. Setting `no_ready_check` to `true` will inhibit this check. |
  224 +| enable_offline_queue | true | By default, if there is no active connection to the Redis server, commands are added to a queue and are executed once the connection has been established. Setting `enable_offline_queue` to `false` will disable this feature and the callback will be executed immediately with an error, or an error will be emitted if no callback is specified. |
  225 +| retry_max_delay | null | __Deprecated__ _Please use `retry_strategy` instead._ By default, every time the client tries to connect and fails, the reconnection delay almost doubles. This delay normally grows infinitely, but setting `retry_max_delay` limits it to the maximum value provided in milliseconds. |
  226 +| connect_timeout | 3600000 | __Deprecated__ _Please use `retry_strategy` instead._ Setting `connect_timeout` limits the total time for the client to connect and reconnect. The value is provided in milliseconds and is counted from the moment a new client is created or from the time the connection is lost. The last retry is going to happen exactly at the timeout time. Default is to try connecting until the default system socket timeout has been exceeded and to try reconnecting until 1h has elapsed. |
  227 +| max_attempts | 0 | __Deprecated__ _Please use `retry_strategy` instead._ By default, a client will try reconnecting until connected. Setting `max_attempts` limits total amount of connection attempts. Setting this to 1 will prevent any reconnect attempt. |
  228 +| retry_unfulfilled_commands | false | If set to `true`, all commands that were unfulfilled while the connection is lost will be retried after the connection has been reestablished. Use this with caution if you use state altering commands (e.g. `incr`). This is especially useful if you use blocking commands. |
  229 +| password | null | If set, client will run Redis auth command on connect. Alias `auth_pass` __Note__ `node_redis` < 2.5 must use `auth_pass` |
  230 +| db | null | If set, client will run Redis `select` command on connect. |
  231 +| family | IPv4 | You can force using IPv6 if you set the family to 'IPv6'. See Node.js [net](https://nodejs.org/api/net.html) or [dns](https://nodejs.org/api/dns.html) modules on how to use the family type. |
  232 +| disable_resubscribing | false | If set to `true`, a client won't resubscribe after disconnecting. |
  233 +| rename_commands | null | Passing an object with renamed commands to use instead of the original functions. For example, if you renamed the command KEYS to "DO-NOT-USE" then the rename_commands object would be: `{ KEYS : "DO-NOT-USE" }` . See the [Redis security topics](http://redis.io/topics/security) for more info. |
  234 +| tls | null | An object containing options to pass to [tls.connect](http://nodejs.org/api/tls.html#tls_tls_connect_port_host_options_callback) to set up a TLS connection to Redis (if, for example, it is set up to be accessible via a tunnel). |
  235 +| prefix | null | A string used to prefix all used keys (e.g. `namespace:test`). Please be aware that the `keys` command will not be prefixed. The `keys` command has a "pattern" as argument and no key and it would be impossible to determine the existing keys in Redis if this would be prefixed. |
  236 +| retry_strategy | function | A function that receives an options object as parameter including the retry `attempt`, the `total_retry_time` indicating how much time passed since the last time connected, the `error` why the connection was lost and the number of `times_connected` in total. If you return a number from this function, the retry will happen exactly after that time in milliseconds. If you return a non-number, no further retry will happen and all offline commands are flushed with errors. Return an error to return that specific error to all offline commands. Example below. |
  237 +
  238 +```js
  239 +var redis = require("redis");
  240 +var client = redis.createClient({detect_buffers: true});
  241 +
  242 +client.set("foo_rand000000000000", "OK");
  243 +
  244 +// This will return a JavaScript String
  245 +client.get("foo_rand000000000000", function (err, reply) {
  246 + console.log(reply.toString()); // Will print `OK`
  247 +});
  248 +
  249 +// This will return a Buffer since original key is specified as a Buffer
  250 +client.get(new Buffer("foo_rand000000000000"), function (err, reply) {
  251 + console.log(reply.toString()); // Will print `<Buffer 4f 4b>`
  252 +});
  253 +client.quit();
  254 +```
  255 +
  256 +retry_strategy example
  257 +
  258 +```js
  259 +var client = redis.createClient({
  260 + retry_strategy: function (options) {
  261 + if (options.error && options.error.code === 'ECONNREFUSED') {
  262 + // End reconnecting on a specific error and flush all commands with
  263 + // a individual error
  264 + return new Error('The server refused the connection');
  265 + }
  266 + if (options.total_retry_time > 1000 * 60 * 60) {
  267 + // End reconnecting after a specific timeout and flush all commands
  268 + // with a individual error
  269 + return new Error('Retry time exhausted');
  270 + }
  271 + if (options.attempt > 10) {
  272 + // End reconnecting with built in error
  273 + return undefined;
  274 + }
  275 + // reconnect after
  276 + return Math.min(options.attempt * 100, 3000);
  277 + }
  278 +});
  279 +```
  280 +
  281 +## client.auth(password[, callback])
  282 +
  283 +When connecting to a Redis server that requires authentication, the `AUTH`
  284 +command must be sent as the first command after connecting. This can be tricky
  285 +to coordinate with reconnections, the ready check, etc. To make this easier,
  286 +`client.auth()` stashes `password` and will send it after each connection,
  287 +including reconnections. `callback` is invoked only once, after the response to
  288 +the very first `AUTH` command sent.
  289 +NOTE: Your call to `client.auth()` should not be inside the ready handler. If
  290 +you are doing this wrong, `client` will emit an error that looks
  291 +something like this `Error: Ready check failed: ERR operation not permitted`.
  292 +
  293 +## backpressure
  294 +
  295 +### stream
  296 +
  297 +The client exposed the used [stream](https://nodejs.org/api/stream.html) in
  298 +`client.stream` and if the stream or client had to
  299 +[buffer](https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback)
  300 +the command in `client.should_buffer`. In combination this can be used to
  301 +implement backpressure by checking the buffer state before sending a command and
  302 +listening to the stream
  303 +[drain](https://nodejs.org/api/stream.html#stream_event_drain) event.
  304 +
  305 +## client.quit()
  306 +
  307 +This sends the quit command to the redis server and ends cleanly right after all
  308 +running commands were properly handled. If this is called while reconnecting
  309 +(and therefore no connection to the redis server exists) it is going to end the
  310 +connection right away instead of resulting in further reconnections! All offline
  311 +commands are going to be flushed with an error in that case.
  312 +
  313 +## client.end(flush)
  314 +
  315 +Forcibly close the connection to the Redis server. Note that this does not wait
  316 +until all replies have been parsed. If you want to exit cleanly, call
  317 +`client.quit()` as mentioned above.
  318 +
  319 +You should set flush to true, if you are not absolutely sure you do not care
  320 +about any other commands. If you set flush to false all still running commands
  321 +will silently fail.
  322 +
  323 +This example closes the connection to the Redis server before the replies have
  324 +been read. You probably don't want to do this:
  325 +
  326 +```js
  327 +var redis = require("redis"),
  328 + client = redis.createClient();
  329 +
  330 +client.set("foo_rand000000000000", "some fantastic value", function (err, reply) {
  331 + // This will either result in an error (flush parameter is set to true)
  332 + // or will silently fail and this callback will not be called at all (flush set to false)
  333 + console.log(err);
  334 +});
  335 +client.end(true); // No further commands will be processed
  336 +client.get("foo_rand000000000000", function (err, reply) {
  337 + console.log(err); // => 'The connection has already been closed.'
  338 +});
  339 +```
  340 +
  341 +`client.end()` without the flush parameter set to true should NOT be used in production!
  342 +
  343 +## Error handling (>= v.2.6)
  344 +
  345 +Currently the following error subclasses exist:
  346 +
  347 +* `RedisError`: _All errors_ returned by the client
  348 +* `ReplyError` subclass of `RedisError`: All errors returned by __Redis__ itself
  349 +* `AbortError` subclass of `RedisError`: All commands that could not finish due
  350 + to what ever reason
  351 +* `ParserError` subclass of `RedisError`: Returned in case of a parser error
  352 + (this should not happen)
  353 +* `AggregateError` subclass of `AbortError`: Emitted in case multiple unresolved
  354 + commands without callback got rejected in debug_mode instead of lots of
  355 + `AbortError`s.
  356 +
  357 +All error classes are exported by the module.
  358 +
  359 +Example:
  360 +```js
  361 +var redis = require('./');
  362 +var assert = require('assert');
  363 +var client = redis.createClient();
  364 +
  365 +client.on('error', function (err) {
  366 + assert(err instanceof Error);
  367 + assert(err instanceof redis.AbortError);
  368 + assert(err instanceof redis.AggregateError);
  369 + // The set and get get aggregated in here
  370 + assert.strictEqual(err.errors.length, 2);
  371 + assert.strictEqual(err.code, 'NR_CLOSED');
  372 +});
  373 +client.set('foo', 123, 'bar', function (err, res) { // Too many arguments
  374 + assert(err instanceof redis.ReplyError); // => true
  375 + assert.strictEqual(err.command, 'SET');
  376 + assert.deepStrictEqual(err.args, ['foo', 123, 'bar']);
  377 +
  378 + redis.debug_mode = true;
  379 + client.set('foo', 'bar');
  380 + client.get('foo');
  381 + process.nextTick(function () {
  382 + // Force closing the connection while the command did not yet return
  383 + client.end(true);
  384 + redis.debug_mode = false;
  385 + });
  386 +});
  387 +
  388 +```
  389 +
  390 +Every `ReplyError` contains the `command` name in all-caps and the arguments (`args`).
  391 +
  392 +If node_redis emits a library error because of another error, the triggering
  393 +error is added to the returned error as `origin` attribute.
  394 +
  395 +___Error codes___
  396 +
  397 +node_redis returns a `NR_CLOSED` error code if the clients connection dropped.
  398 +If a command unresolved command got rejected a `UNCERTAIN_STATE` code is
  399 +returned. A `CONNECTION_BROKEN` error code is used in case node_redis gives up
  400 +to reconnect.
  401 +
  402 +## client.unref()
  403 +
  404 +Call `unref()` on the underlying socket connection to the Redis server, allowing
  405 +the program to exit once no more commands are pending.
  406 +
  407 +This is an **experimental** feature, and only supports a subset of the Redis
  408 +protocol. Any commands where client state is saved on the Redis server, e.g.
  409 +`*SUBSCRIBE` or the blocking `BL*` commands will *NOT* work with `.unref()`.
  410 +
  411 +```js
  412 +var redis = require("redis")
  413 +var client = redis.createClient()
  414 +
  415 +/*
  416 + Calling unref() will allow this program to exit immediately after the get
  417 + command finishes. Otherwise the client would hang as long as the
  418 + client-server connection is alive.
  419 +*/
  420 +client.unref()
  421 +client.get("foo", function (err, value){
  422 + if (err) throw(err)
  423 + console.log(value)
  424 +})
  425 +```
  426 +
  427 +## Friendlier hash commands
  428 +
  429 +Most Redis commands take a single String or an Array of Strings as arguments,
  430 +and replies are sent back as a single String or an Array of Strings. When
  431 +dealing with hash values, there are a couple of useful exceptions to this.
  432 +
  433 +### client.hgetall(hash, callback)
  434 +
  435 +The reply from an HGETALL command will be converted into a JavaScript Object by
  436 +`node_redis`. That way you can interact with the responses using JavaScript
  437 +syntax.
  438 +
  439 +Example:
  440 +
  441 +```js
  442 +client.hmset("hosts", "mjr", "1", "another", "23", "home", "1234");
  443 +client.hgetall("hosts", function (err, obj) {
  444 + console.dir(obj);
  445 +});
  446 +```
  447 +
  448 +Output:
  449 +
  450 +```js
  451 +{ mjr: '1', another: '23', home: '1234' }
  452 +```
  453 +
  454 +### client.hmset(hash, obj[, callback])
  455 +
  456 +Multiple values in a hash can be set by supplying an object:
  457 +
  458 +```js
  459 +client.HMSET(key2, {
  460 + "0123456789": "abcdefghij", // NOTE: key and value will be coerced to strings
  461 + "some manner of key": "a type of value"
  462 +});
  463 +```
  464 +
  465 +The properties and values of this Object will be set as keys and values in the
  466 +Redis hash.
  467 +
  468 +### client.hmset(hash, key1, val1, ... keyn, valn, [callback])
  469 +
  470 +Multiple values may also be set by supplying a list:
  471 +
  472 +```js
  473 +client.HMSET(key1, "0123456789", "abcdefghij", "some manner of key", "a type of value");
  474 +```
  475 +
  476 +## Publish / Subscribe
  477 +
  478 +Example of the publish / subscribe API. This program opens two
  479 +client connections, subscribes to a channel on one of them, and publishes to that
  480 +channel on the other:
  481 +
  482 +```js
  483 +var redis = require("redis");
  484 +var sub = redis.createClient(), pub = redis.createClient();
  485 +var msg_count = 0;
  486 +
  487 +sub.on("subscribe", function (channel, count) {
  488 + pub.publish("a nice channel", "I am sending a message.");
  489 + pub.publish("a nice channel", "I am sending a second message.");
  490 + pub.publish("a nice channel", "I am sending my last message.");
  491 +});
  492 +
  493 +sub.on("message", function (channel, message) {
  494 + console.log("sub channel " + channel + ": " + message);
  495 + msg_count += 1;
  496 + if (msg_count === 3) {
  497 + sub.unsubscribe();
  498 + sub.quit();
  499 + pub.quit();
  500 + }
  501 +});
  502 +
  503 +sub.subscribe("a nice channel");
  504 +```
  505 +
  506 +When a client issues a `SUBSCRIBE` or `PSUBSCRIBE`, that connection is put into
  507 +a "subscriber" mode. At that point, only commands that modify the subscription
  508 +set are valid and quit (and depending on the redis version ping as well). When
  509 +the subscription set is empty, the connection is put back into regular mode.
  510 +
  511 +If you need to send regular commands to Redis while in subscriber mode, just
  512 +open another connection with a new client (hint: use `client.duplicate()`).
  513 +
  514 +## Subscriber Events
  515 +
  516 +If a client has subscriptions active, it may emit these events:
  517 +
  518 +### "message" (channel, message)
  519 +
  520 +Client will emit `message` for every message received that matches an active subscription.
  521 +Listeners are passed the channel name as `channel` and the message as `message`.
  522 +
  523 +### "pmessage" (pattern, channel, message)
  524 +
  525 +Client will emit `pmessage` for every message received that matches an active
  526 +subscription pattern. Listeners are passed the original pattern used with
  527 +`PSUBSCRIBE` as `pattern`, the sending channel name as `channel`, and the
  528 +message as `message`.
  529 +
  530 +### "message_buffer" (channel, message)
  531 +
  532 +This is the same as the `message` event with the exception, that it is always
  533 +going to emit a buffer. If you listen to the `message` event at the same time as
  534 +the `message_buffer`, it is always going to emit a string.
  535 +
  536 +### "pmessage_buffer" (pattern, channel, message)
  537 +
  538 +This is the same as the `pmessage` event with the exception, that it is always
  539 +going to emit a buffer. If you listen to the `pmessage` event at the same time
  540 +as the `pmessage_buffer`, it is always going to emit a string.
  541 +
  542 +### "subscribe" (channel, count)
  543 +
  544 +Client will emit `subscribe` in response to a `SUBSCRIBE` command. Listeners are
  545 +passed the channel name as `channel` and the new count of subscriptions for this
  546 +client as `count`.
  547 +
  548 +### "psubscribe" (pattern, count)
  549 +
  550 +Client will emit `psubscribe` in response to a `PSUBSCRIBE` command. Listeners
  551 +are passed the original pattern as `pattern`, and the new count of subscriptions
  552 +for this client as `count`.
  553 +
  554 +### "unsubscribe" (channel, count)
  555 +
  556 +Client will emit `unsubscribe` in response to a `UNSUBSCRIBE` command. Listeners
  557 +are passed the channel name as `channel` and the new count of subscriptions for
  558 +this client as `count`. When `count` is 0, this client has left subscriber mode
  559 +and no more subscriber events will be emitted.
  560 +
  561 +### "punsubscribe" (pattern, count)
  562 +
  563 +Client will emit `punsubscribe` in response to a `PUNSUBSCRIBE` command.
  564 +Listeners are passed the channel name as `channel` and the new count of
  565 +subscriptions for this client as `count`. When `count` is 0, this client has
  566 +left subscriber mode and no more subscriber events will be emitted.
  567 +
  568 +## client.multi([commands])
  569 +
  570 +`MULTI` commands are queued up until an `EXEC` is issued, and then all commands
  571 +are run atomically by Redis. The interface in `node_redis` is to return an
  572 +individual `Multi` object by calling `client.multi()`. If any command fails to
  573 +queue, all commands are rolled back and none is going to be executed (For
  574 +further information look at
  575 +[transactions](http://redis.io/topics/transactions)).
  576 +
  577 +```js
  578 +var redis = require("./index"),
  579 + client = redis.createClient(), set_size = 20;
  580 +
  581 +client.sadd("bigset", "a member");
  582 +client.sadd("bigset", "another member");
  583 +
  584 +while (set_size > 0) {
  585 + client.sadd("bigset", "member " + set_size);
  586 + set_size -= 1;
  587 +}
  588 +
  589 +// multi chain with an individual callback
  590 +client.multi()
  591 + .scard("bigset")
  592 + .smembers("bigset")
  593 + .keys("*", function (err, replies) {
  594 + // NOTE: code in this callback is NOT atomic
  595 + // this only happens after the the .exec call finishes.
  596 + client.mget(replies, redis.print);
  597 + })
  598 + .dbsize()
  599 + .exec(function (err, replies) {
  600 + console.log("MULTI got " + replies.length + " replies");
  601 + replies.forEach(function (reply, index) {
  602 + console.log("Reply " + index + ": " + reply.toString());
  603 + });
  604 + });
  605 +```
  606 +
  607 +### Multi.exec([callback])
  608 +
  609 +`client.multi()` is a constructor that returns a `Multi` object. `Multi` objects
  610 +share all of the same command methods as `client` objects do. Commands are
  611 +queued up inside the `Multi` object until `Multi.exec()` is invoked.
  612 +
  613 +If your code contains an syntax error an EXECABORT error is going to be thrown
  614 +and all commands are going to be aborted. That error contains a `.errors`
  615 +property that contains the concrete errors.
  616 +If all commands were queued successfully and an error is thrown by redis while
  617 +processing the commands that error is going to be returned in the result array!
  618 +No other command is going to be aborted though than the onces failing.
  619 +
  620 +You can either chain together `MULTI` commands as in the above example, or you
  621 +can queue individual commands while still sending regular client command as in
  622 +this example:
  623 +
  624 +```js
  625 +var redis = require("redis"),
  626 + client = redis.createClient(), multi;
  627 +
  628 +// start a separate multi command queue
  629 +multi = client.multi();
  630 +multi.incr("incr thing", redis.print);
  631 +multi.incr("incr other thing", redis.print);
  632 +
  633 +// runs immediately
  634 +client.mset("incr thing", 100, "incr other thing", 1, redis.print);
  635 +
  636 +// drains multi queue and runs atomically
  637 +multi.exec(function (err, replies) {
  638 + console.log(replies); // 101, 2
  639 +});
  640 +```
  641 +
  642 +In addition to adding commands to the `MULTI` queue individually, you can also
  643 +pass an array of commands and arguments to the constructor:
  644 +
  645 +```js
  646 +var redis = require("redis"),
  647 + client = redis.createClient(), multi;
  648 +
  649 +client.multi([
  650 + ["mget", "multifoo", "multibar", redis.print],
  651 + ["incr", "multifoo"],
  652 + ["incr", "multibar"]
  653 +]).exec(function (err, replies) {
  654 + console.log(replies);
  655 +});
  656 +```
  657 +
  658 +### Multi.exec_atomic([callback])
  659 +
  660 +Identical to Multi.exec but with the difference that executing a single command
  661 +will not use transactions.
  662 +
  663 +## client.batch([commands])
  664 +
  665 +Identical to .multi without transactions. This is recommended if you want to
  666 +execute many commands at once but don't have to rely on transactions.
  667 +
  668 +`BATCH` commands are queued up until an `EXEC` is issued, and then all commands
  669 +are run atomically by Redis. The interface in `node_redis` is to return an
  670 +individual `Batch` object by calling `client.batch()`. The only difference
  671 +between .batch and .multi is that no transaction is going to be used.
  672 +Be aware that the errors are - just like in multi statements - in the result.
  673 +Otherwise both, errors and results could be returned at the same time.
  674 +
  675 +If you fire many commands at once this is going to boost the execution speed
  676 +significantly compared to firing the same commands in a loop without waiting for
  677 +the result! See the benchmarks for further comparison. Please remember that all
  678 +commands are kept in memory until they are fired.
  679 +
  680 +## Monitor mode
  681 +
  682 +Redis supports the `MONITOR` command, which lets you see all commands received
  683 +by the Redis server across all client connections, including from other client
  684 +libraries and other computers.
  685 +
  686 +A `monitor` event is going to be emitted for every command fired from any client
  687 +connected to the server including the monitoring client itself. The callback for
  688 +the `monitor` event takes a timestamp from the Redis server, an array of command
  689 +arguments and the raw monitoring string.
  690 +
  691 +Example:
  692 +
  693 +```js
  694 +var client = require("redis").createClient();
  695 +client.monitor(function (err, res) {
  696 + console.log("Entering monitoring mode.");
  697 +});
  698 +client.set('foo', 'bar');
  699 +
  700 +client.on("monitor", function (time, args, raw_reply) {
  701 + console.log(time + ": " + args); // 1458910076.446514:['set', 'foo', 'bar']
  702 +});
  703 +```
  704 +
  705 +# Extras
  706 +
  707 +Some other things you might like to know about.
  708 +
  709 +## client.server_info
  710 +
  711 +After the ready probe completes, the results from the INFO command are saved in
  712 +the `client.server_info` object.
  713 +
  714 +The `versions` key contains an array of the elements of the version string for
  715 +easy comparison.
  716 +
  717 + > client.server_info.redis_version
  718 + '2.3.0'
  719 + > client.server_info.versions
  720 + [ 2, 3, 0 ]
  721 +
  722 +## redis.print()
  723 +
  724 +A handy callback function for displaying return values when testing. Example:
  725 +
  726 +```js
  727 +var redis = require("redis"),
  728 + client = redis.createClient();
  729 +
  730 +client.on("connect", function () {
  731 + client.set("foo_rand000000000000", "some fantastic value", redis.print);
  732 + client.get("foo_rand000000000000", redis.print);
  733 +});
  734 +```
  735 +
  736 +This will print:
  737 +
  738 + Reply: OK
  739 + Reply: some fantastic value
  740 +
  741 +Note that this program will not exit cleanly because the client is still connected.
  742 +
  743 +## Multi-word commands
  744 +
  745 +To execute redis multi-word commands like `SCRIPT LOAD` or `CLIENT LIST` pass
  746 +the second word as first parameter:
  747 +
  748 + client.script('load', 'return 1');
  749 + client.multi().script('load', 'return 1').exec(...);
  750 + client.multi([['script', 'load', 'return 1']]).exec(...);
  751 +
  752 +## client.duplicate([options][, callback])
  753 +
  754 +Duplicate all current options and return a new redisClient instance. All options
  755 +passed to the duplicate function are going to replace the original option. If
  756 +you pass a callback, duplicate is going to wait until the client is ready and
  757 +returns it in the callback. If an error occurs in the meanwhile, that is going
  758 +to return an error instead in the callback.
  759 +
  760 +One example of when to use duplicate() would be to accommodate the connection-
  761 +blocking redis commands BRPOP, BLPOP, and BRPOPLPUSH. If these commands
  762 +are used on the same redisClient instance as non-blocking commands, the
  763 +non-blocking ones may be queued up until after the blocking ones finish.
  764 +
  765 + var Redis=require('redis');
  766 + var client = Redis.createClient();
  767 + var clientBlocking = client.duplicate();
  768 +
  769 + var get = function() {
  770 + console.log("get called");
  771 + client.get("any_key",function() { console.log("get returned"); });
  772 + setTimeout( get, 1000 );
  773 + };
  774 + var brpop = function() {
  775 + console.log("brpop called");
  776 + clientBlocking.brpop("nonexistent", 5, function() {
  777 + console.log("brpop return");
  778 + setTimeout( brpop, 1000 );
  779 + });
  780 + };
  781 + get();
  782 + brpop();
  783 +
  784 +Another reason to use duplicate() is when multiple DBs on the same server are
  785 +accessed via the redis SELECT command. Each DB could use its own connection.
  786 +
  787 +## client.send_command(command_name[, [args][, callback]])
  788 +
  789 +All Redis commands have been added to the `client` object. However, if new
  790 +commands are introduced before this library is updated or if you want to add
  791 +individual commands you can use `send_command()` to send arbitrary commands to
  792 +Redis.
  793 +
  794 +All commands are sent as multi-bulk commands. `args` can either be an Array of
  795 +arguments, or omitted / set to undefined.
  796 +
  797 +## client.add_command(command_name)
  798 +
  799 +Calling add_command will add a new command to the prototype. The exact command
  800 +name will be used when calling using this new command. Using arbitrary arguments
  801 +is possible as with any other command.
  802 +
  803 +## client.connected
  804 +
  805 +Boolean tracking the state of the connection to the Redis server.
  806 +
  807 +## client.command_queue_length
  808 +
  809 +The number of commands that have been sent to the Redis server but not yet
  810 +replied to. You can use this to enforce some kind of maximum queue depth for
  811 +commands while connected.
  812 +
  813 +## client.offline_queue_length
  814 +
  815 +The number of commands that have been queued up for a future connection. You can
  816 +use this to enforce some kind of maximum queue depth for pre-connection
  817 +commands.
  818 +
  819 +### Commands with Optional and Keyword arguments
  820 +
  821 +This applies to anything that uses an optional `[WITHSCORES]` or `[LIMIT offset
  822 +count]` in the [redis.io/commands](http://redis.io/commands) documentation.
  823 +
  824 +Example:
  825 +
  826 +```js
  827 +var args = [ 'myzset', 1, 'one', 2, 'two', 3, 'three', 99, 'ninety-nine' ];
  828 +client.zadd(args, function (err, response) {
  829 + if (err) throw err;
  830 + console.log('added '+response+' items.');
  831 +
  832 + // -Infinity and +Infinity also work
  833 + var args1 = [ 'myzset', '+inf', '-inf' ];
  834 + client.zrevrangebyscore(args1, function (err, response) {
  835 + if (err) throw err;
  836 + console.log('example1', response);
  837 + // write your code here
  838 + });
  839 +
  840 + var max = 3, min = 1, offset = 1, count = 2;
  841 + var args2 = [ 'myzset', max, min, 'WITHSCORES', 'LIMIT', offset, count ];
  842 + client.zrevrangebyscore(args2, function (err, response) {
  843 + if (err) throw err;
  844 + console.log('example2', response);
  845 + // write your code here
  846 + });
  847 +});
  848 +```
  849 +
  850 +## Performance
  851 +
  852 +Much effort has been spent to make `node_redis` as fast as possible for common
  853 +operations.
  854 +
  855 +```
  856 +Lenovo T450s, i7-5600U and 12gb memory
  857 +clients: 1, NodeJS: 6.2.0, Redis: 3.2.0, parser: javascript, connected by: tcp
  858 + PING, 1/1 avg/max: 0.02/ 5.26 2501ms total, 46916 ops/sec
  859 + PING, batch 50/1 avg/max: 0.06/ 4.35 2501ms total, 755178 ops/sec
  860 + SET 4B str, 1/1 avg/max: 0.02/ 4.75 2501ms total, 40856 ops/sec
  861 + SET 4B str, batch 50/1 avg/max: 0.11/ 1.51 2501ms total, 432727 ops/sec
  862 + SET 4B buf, 1/1 avg/max: 0.05/ 2.76 2501ms total, 20659 ops/sec
  863 + SET 4B buf, batch 50/1 avg/max: 0.25/ 1.76 2501ms total, 194962 ops/sec
  864 + GET 4B str, 1/1 avg/max: 0.02/ 1.55 2501ms total, 45156 ops/sec
  865 + GET 4B str, batch 50/1 avg/max: 0.09/ 3.15 2501ms total, 524110 ops/sec
  866 + GET 4B buf, 1/1 avg/max: 0.02/ 3.07 2501ms total, 44563 ops/sec
  867 + GET 4B buf, batch 50/1 avg/max: 0.10/ 3.18 2501ms total, 473171 ops/sec
  868 + SET 4KiB str, 1/1 avg/max: 0.03/ 1.54 2501ms total, 32627 ops/sec
  869 + SET 4KiB str, batch 50/1 avg/max: 0.34/ 1.89 2501ms total, 146861 ops/sec
  870 + SET 4KiB buf, 1/1 avg/max: 0.05/ 2.85 2501ms total, 20688 ops/sec
  871 + SET 4KiB buf, batch 50/1 avg/max: 0.36/ 1.83 2501ms total, 138165 ops/sec
  872 + GET 4KiB str, 1/1 avg/max: 0.02/ 1.37 2501ms total, 39389 ops/sec
  873 + GET 4KiB str, batch 50/1 avg/max: 0.24/ 1.81 2501ms total, 208157 ops/sec
  874 + GET 4KiB buf, 1/1 avg/max: 0.02/ 2.63 2501ms total, 39918 ops/sec
  875 + GET 4KiB buf, batch 50/1 avg/max: 0.31/ 8.56 2501ms total, 161575 ops/sec
  876 + INCR, 1/1 avg/max: 0.02/ 4.69 2501ms total, 45685 ops/sec
  877 + INCR, batch 50/1 avg/max: 0.09/ 3.06 2501ms total, 539964 ops/sec
  878 + LPUSH, 1/1 avg/max: 0.02/ 3.04 2501ms total, 41253 ops/sec
  879 + LPUSH, batch 50/1 avg/max: 0.12/ 1.94 2501ms total, 425090 ops/sec
  880 + LRANGE 10, 1/1 avg/max: 0.02/ 2.28 2501ms total, 39850 ops/sec
  881 + LRANGE 10, batch 50/1 avg/max: 0.25/ 1.85 2501ms total, 194302 ops/sec
  882 + LRANGE 100, 1/1 avg/max: 0.05/ 2.93 2501ms total, 21026 ops/sec
  883 + LRANGE 100, batch 50/1 avg/max: 1.52/ 2.89 2501ms total, 32767 ops/sec
  884 + SET 4MiB str, 1/1 avg/max: 5.16/ 15.55 2502ms total, 193 ops/sec
  885 + SET 4MiB str, batch 20/1 avg/max: 89.73/ 99.96 2513ms total, 223 ops/sec
  886 + SET 4MiB buf, 1/1 avg/max: 2.23/ 8.35 2501ms total, 446 ops/sec
  887 + SET 4MiB buf, batch 20/1 avg/max: 41.47/ 50.91 2530ms total, 482 ops/sec
  888 + GET 4MiB str, 1/1 avg/max: 2.79/ 10.91 2502ms total, 358 ops/sec
  889 + GET 4MiB str, batch 20/1 avg/max: 101.61/118.11 2541ms total, 197 ops/sec
  890 + GET 4MiB buf, 1/1 avg/max: 2.32/ 14.93 2502ms total, 430 ops/sec
  891 + GET 4MiB buf, batch 20/1 avg/max: 65.01/ 84.72 2536ms total, 308 ops/sec
  892 + ```
  893 +
  894 +## Debugging
  895 +
  896 +To get debug output run your `node_redis` application with `NODE_DEBUG=redis`.
  897 +
  898 +This is also going to result in good stack traces opposed to useless ones
  899 +otherwise for any async operation.
  900 +If you only want to have good stack traces but not the debug output run your
  901 +application in development mode instead (`NODE_ENV=development`).
  902 +
  903 +Good stack traces are only activated in development and debug mode as this
  904 +results in a significant performance penalty.
  905 +
  906 +___Comparison___:
  907 +Useless stack trace:
  908 +```
  909 +ReplyError: ERR wrong number of arguments for 'set' command
  910 + at parseError (/home/ruben/repos/redis/node_modules/redis-parser/lib/parser.js:158:12)
  911 + at parseType (/home/ruben/repos/redis/node_modules/redis-parser/lib/parser.js:219:14)
  912 +```
  913 +Good stack trace:
  914 +```
  915 +ReplyError: ERR wrong number of arguments for 'set' command
  916 + at new Command (/home/ruben/repos/redis/lib/command.js:9:902)
  917 + at RedisClient.set (/home/ruben/repos/redis/lib/commands.js:9:3238)
  918 + at Context.<anonymous> (/home/ruben/repos/redis/test/good_stacks.spec.js:20:20)
  919 + at callFnAsync (/home/ruben/repos/redis/node_modules/mocha/lib/runnable.js:349:8)
  920 + at Test.Runnable.run (/home/ruben/repos/redis/node_modules/mocha/lib/runnable.js:301:7)
  921 + at Runner.runTest (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:422:10)
  922 + at /home/ruben/repos/redis/node_modules/mocha/lib/runner.js:528:12
  923 + at next (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:342:14)
  924 + at /home/ruben/repos/redis/node_modules/mocha/lib/runner.js:352:7
  925 + at next (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:284:14)
  926 + at Immediate._onImmediate (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:320:5)
  927 + at processImmediate [as _immediateCallback] (timers.js:383:17)
  928 +```
  929 +
  930 +## How to Contribute
  931 +- Open a pull request or an issue about what you want to implement / change. We're glad for any help!
  932 + - Please be aware that we'll only accept fully tested code.
  933 +
  934 +## Contributors
  935 +
  936 +The original author of node_redis is [Matthew Ranney](https://github.com/mranney)
  937 +
  938 +The current lead maintainer is [Ruben Bridgewater](https://github.com/BridgeAR)
  939 +
  940 +Many [others](https://github.com/NodeRedis/node_redis/graphs/contributors)
  941 +contributed to `node_redis` too. Thanks to all of them!
  942 +
  943 +## License
  944 +
  945 +[MIT](LICENSE)
  946 +
  947 +### Consolidation: It's time for celebration
  948 +
  949 +Right now there are two great redis clients around and both have some advantages
  950 +above each other. We speak about ioredis and node_redis. So after talking to
  951 +each other about how we could improve in working together we (that is @luin and
  952 +@BridgeAR) decided to work towards a single library on the long run. But step by
  953 +step.
  954 +
  955 +First of all, we want to split small parts of our libraries into others so that
  956 +we're both able to use the same code. Those libraries are going to be maintained
  957 +under the NodeRedis organization. This is going to reduce the maintenance
  958 +overhead, allows others to use the very same code, if they need it and it's way
  959 +easyer for others to contribute to both libraries.
  960 +
  961 +We're very happy about this step towards working together as we both want to
  962 +give you the best redis experience possible.
  963 +
  964 +If you want to join our cause by help maintaining something, please don't
  965 +hesitate to contact either one of us.
  1 +# Changelog
  2 +
  3 +## v.2.8.0 - 31 Jul, 2017
  4 +
  5 +Features
  6 +
  7 +- Accept UPPER_CASE commands in send_command
  8 +- Add arbitrary commands to the prototype by using `Redis.addCommand(name)`
  9 +
  10 +Bugfixes
  11 +
  12 +- Fixed not always copying subscribe unsubscribe arguments
  13 +- Fixed emitting internal errors while reconnecting with auth
  14 +- Fixed crashing with invalid url option
  15 +
  16 +## v.2.7.1 - 14 Mar, 2017
  17 +
  18 +Bugfixes
  19 +
  20 +- Fixed monitor mode not working in combination with IPv6 (2.6.0 regression)
  21 +
  22 +## v.2.7.0 - 11 Mar, 2017
  23 +
  24 +Features
  25 +
  26 +- All returned errors are from now a subclass of `RedisError`.
  27 +
  28 +Bugfixes
  29 +
  30 +- Fixed rename_commands not accepting `null` as value
  31 +- Fixed `AbortError`s and `AggregateError`s not showing the error message in the stack trace
  32 +
  33 +## v.2.6.5 - 15 Jan, 2017
  34 +
  35 +Bugfixes
  36 +
  37 +- Fixed parser not being reset in case the redis connection closed ASAP for overcoming of output buffer limits
  38 +- Fixed parser reset if (p)message_buffer listener is attached
  39 +
  40 +## v.2.6.4 - 12 Jan, 2017
  41 +
  42 +Bugfixes
  43 +
  44 +- Fixed monitor mode not working in combination with IPv6, sockets or lua scripts (2.6.0 regression)
  45 +
  46 +## v.2.6.3 - 31 Oct, 2016
  47 +
  48 +Bugfixes
  49 +
  50 +- Do not change the tls setting to camel_case
  51 +- Fix domain handling in combination with the offline queue (2.5.3 regression)
  52 +
  53 +## v.2.6.2 - 16 Jun, 2016
  54 +
  55 +Bugfixes
  56 +
  57 +- Fixed individual callbacks of a transaction not being called (2.6.0 regression)
  58 +
  59 +## v.2.6.1 - 02 Jun, 2016
  60 +
  61 +Bugfixes
  62 +
  63 +- Fixed invalid function name being exported
  64 +
  65 +## v.2.6.0 - 01 Jun, 2016
  66 +
  67 +In addition to the pre-releases the following changes exist in v.2.6.0:
  68 +
  69 +Features
  70 +
  71 +- Updated [redis-parser](https://github.com/NodeRedis/node-redis-parser) dependency ([changelog](https://github.com/NodeRedis/node-redis-parser/releases/tag/v.2.0.0))
  72 + - The JS parser is from now on the new default as it is a lot faster than the hiredis parser
  73 + - This is no BC as there is no changed behavior for the user at all but just a performance improvement. Explicitly requireing the Hiredis parser is still possible.
  74 +- Added name property to all Redis functions (Node.js >= 4.0)
  75 +- Improved stack traces in development and debug mode
  76 +
  77 +Bugfixes
  78 +
  79 +- Reverted support for `__proto__` (v.2.6.0-2) to prevent and breaking change
  80 +
  81 +Deprecations
  82 +
  83 +- The `parser` option is deprecated and should be removed. The built-in Javascript parser is a lot faster than the hiredis parser and has more features
  84 +
  85 +## v.2.6.0-2 - 29 Apr, 2016
  86 +
  87 +Features
  88 +
  89 +- Added support for the new [CLIENT REPLY ON|OFF|SKIP](http://redis.io/commands/client-reply) command (Redis v.3.2)
  90 +- Added support for camelCase
  91 + - The Node.js landscape default is to use camelCase. node_redis is a bit out of the box here
  92 + but from now on it is possible to use both, just as you prefer!
  93 + - If there's any documented variable missing as camelCased, please open a issue for it
  94 +- Improve error handling significantly
  95 + - Only emit an error if the error has not already been handled in a callback
  96 + - Improved unspecific error messages e.g. "Connection gone from end / close event"
  97 + - Added `args` to command errors to improve identification of the error
  98 + - Added origin to errors if there's e.g. a connection error
  99 + - Added ReplyError class. All Redis errors are from now on going to be of that class
  100 + - Added AbortError class. A subclass of AbortError. All unresolved and by node_redis rejected commands are from now on of that class
  101 + - Added AggregateError class. If a unresolved and by node_redis rejected command has no callback and
  102 + this applies to more than a single command, the errors for the commands without callback are aggregated
  103 + to a single error that is emitted in debug_mode in that case.
  104 +- Added `message_buffer` / `pmessage_buffer` events. That event is always going to emit a buffer
  105 + - Listening to the `message` event at the same time is always going to return the same message as string
  106 +- Added callback option to the duplicate function
  107 +- Added support for `__proto__` and other reserved keywords as hgetall field
  108 +- Updated [redis-commands](https://github.com/NodeRedis/redis-commands) dependency ([changelog](https://github.com/NodeRedis/redis-commands/releases/tag/v.1.2.0))
  109 +
  110 +Bugfixes
  111 +
  112 +- Fixed v.2.5.0 auth command regression (under special circumstances a reconnect would not authenticate properly)
  113 +- Fixed v.2.6.0-0 pub sub mode and quit command regressions:
  114 + - Entering pub sub mode not working if a earlier called and still running command returned an error
  115 + - Unsubscribe callback not called if unsubscribing from all channels and resubscribing right away
  116 + - Quit command resulting in an error in some cases
  117 +- Fixed special handled functions in batch and multi context not working the same as without (e.g. select and info)
  118 + - Be aware that not all commands work in combination with transactions but they all work with batch
  119 +- Fixed address always set to 127.0.0.1:6379 in case host / port is set in the `tls` options instead of the general options
  120 +
  121 +## v.2.6.0-1 - 01 Apr, 2016
  122 +
  123 +A second pre-release with further fixes. This is likely going to be released as 2.6.0 stable without further changes.
  124 +
  125 +Features
  126 +
  127 +- Added type validations for client.send_command arguments
  128 +
  129 +Bugfixes
  130 +
  131 +- Fixed client.send_command not working properly with every command and every option
  132 +- Fixed pub sub mode unsubscribing from all channels in combination with the new `string_numbers` option crashing
  133 +- Fixed pub sub mode unsubscribing from all channels not respected while reconnecting
  134 +- Fixed pub sub mode events in combination with the `string_numbers` option emitting the number of channels not as number
  135 +
  136 +## v.2.6.0-0 - 27 Mar, 2016
  137 +
  138 +This is mainly a very important bug fix release with some smaller features.
  139 +
  140 +Features
  141 +
  142 +- Monitor and pub sub mode now work together with the offline queue
  143 + - All commands that were send after a connection loss are now going to be send after reconnecting
  144 +- Activating monitor mode does now work together with arbitrary commands including pub sub mode
  145 +- Pub sub mode is completely rewritten and all known issues fixed
  146 +- Added `string_numbers` option to get back strings instead of numbers
  147 +- Quit command is from now on always going to end the connection properly
  148 +
  149 +Bugfixes
  150 +
  151 +- Fixed calling monitor command while other commands are still running
  152 +- Fixed monitor and pub sub mode not working together
  153 +- Fixed monitor mode not working in combination with the offline queue
  154 +- Fixed pub sub mode not working in combination with the offline queue
  155 +- Fixed pub sub mode resubscribing not working with non utf8 buffer channels
  156 +- Fixed pub sub mode crashing if calling unsubscribe / subscribe in various combinations
  157 +- Fixed pub sub mode emitting unsubscribe even if no channels were unsubscribed
  158 +- Fixed pub sub mode emitting a message without a message published
  159 +- Fixed quit command not ending the connection and resulting in further reconnection if called while reconnecting
  160 +
  161 +The quit command did not end connections earlier if the connection was down at that time and this could have
  162 +lead to strange situations, therefor this was fixed to end the connection right away in those cases.
  163 +
  164 +## v.2.5.3 - 21 Mar, 2016
  165 +
  166 +Bugfixes
  167 +
  168 +- Revert throwing on invalid data types and print a warning instead
  169 +
  170 +## v.2.5.2 - 16 Mar, 2016
  171 +
  172 +Bugfixes
  173 +
  174 +- Fixed breaking changes against Redis 2.4 introduced in 2.5.0 / 2.5.1
  175 +
  176 +## v.2.5.1 - 15 Mar, 2016
  177 +
  178 +Bugfixes
  179 +
  180 +- Fixed info command not working anymore with optional section argument
  181 +
  182 +## v.2.5.0 - 15 Mar, 2016
  183 +
  184 +Same changelog as the pre-release
  185 +
  186 +## v.2.5.0-1 - 07 Mar, 2016
  187 +
  188 +This is a big release with some substantial underlining changes. Therefor this is released as a pre-release and I encourage anyone who's able to, to test this out.
  189 +
  190 +It took way to long to release this one and the next release cycles will be shorter again.
  191 +
  192 +This release is also going to deprecate a couple things to prepare for a future v.3 (it'll still take a while to v.3).
  193 +
  194 +Features
  195 +
  196 +- The parsers moved into the [redis-parser](https://github.com/NodeRedis/node-redis-parser) module and will be maintained in there from now on
  197 + - Improve js parser speed significantly for big SUNION/SINTER/LRANGE/ZRANGE
  198 +- Improve redis-url parsing to also accept the database-number and options as query parameters as suggested in [IANA](http://www.iana.org/assignments/uri-schemes/prov/redis)
  199 +- Added a `retry_unfulfilled_commands` option
  200 + - Setting this to 'true' results in retrying all commands that were not fulfilled on a connection loss after the reconnect. Use with caution
  201 +- Added a `db` option to select the database while connecting (this is [not recommended](https://groups.google.com/forum/#!topic/redis-db/vS5wX8X4Cjg))
  202 +- Added a `password` option as alias for auth_pass
  203 +- The client.server_info is from now on updated while using the info command
  204 +- Gracefuly handle redis protocol errors from now on
  205 +- Added a `warning` emitter that receives node_redis warnings like auth not required and deprecation messages
  206 +- Added a `retry_strategy` option that replaces all reconnect options
  207 +- The reconnecting event from now on also receives:
  208 + - The error message why the reconnect happened (params.error)
  209 + - The amount of times the client was connected (params.times_connected)
  210 + - The total reconnecting time since the last time connected (params.total_retry_time)
  211 +- Always respect the command execution order no matter if the reply could be returned sync or not (former exceptions: [#937](https://github.com/NodeRedis/node_redis/issues/937#issuecomment-167525939))
  212 +- redis.createClient is now checking input values stricter and detects more faulty input
  213 +- Started refactoring internals into individual modules
  214 +- Pipelining speed improvements
  215 +
  216 +Bugfixes
  217 +
  218 +- Fixed explicit undefined as a command callback in a multi context
  219 +- Fixed hmset failing to detect the first key as buffer or date if the key is of that type
  220 +- Fixed do not run toString on an array argument and throw a "invalid data" error instead
  221 + - This is not considered as breaking change, as this is likely a error in your code and if you want to have such a behavior you should handle this beforehand
  222 + - The same applies to Map / Set and individual Object types
  223 +- Fixed redis url not accepting the protocol being omitted or protocols other than the redis protocol for convenience
  224 +- Fixed parsing the db keyspace even if the first database does not begin with a zero
  225 +- Fixed handling of errors occurring while receiving pub sub messages
  226 +- Fixed huge string pipelines crashing NodeJS (Pipeline size above 256mb)
  227 +- Fixed rename_commands and prefix option not working together
  228 +- Fixed ready being emitted to early in case a slave is still syncing / master down
  229 +
  230 +Deprecations
  231 +
  232 +- Using any command with a argument being set to null or undefined is deprecated
  233 + - From v.3.0.0 on using a command with such an argument will return an error instead
  234 + - If you want to keep the old behavior please use a precheck in your code that converts the arguments to a string.
  235 + - Using SET or SETEX with a undefined or null value will from now on also result in converting the value to "null" / "undefined" to have a consistent behavior. This is not considered as breaking change, as it returned an error earlier.
  236 +- Using .end(flush) without the flush parameter is deprecated and the flush parameter should explicitly be used
  237 + - From v.3.0.0 on using .end without flush will result in an error
  238 + - Using .end without flush means that any command that did not yet return is going to silently fail. Therefor this is considered harmful and you should explicitly silence such errors if you are sure you want this
  239 +- Depending on the return value of a command to detect the backpressure is deprecated
  240 + - From version 3.0.0 on node_redis might not return true / false as a return value anymore. Please rely on client.should_buffer instead
  241 +- The `socket_nodelay` option is deprecated and will be removed in v.3.0.0
  242 + - If you want to buffer commands you should use [.batch or .multi](./README.md) instead. This is necessary to reduce the amount of different options and this is very likely reducing your throughput if set to false.
  243 + - If you are sure you want to activate the NAGLE algorithm you can still activate it by using client.stream.setNoDelay(false)
  244 +- The `max_attempts` option is deprecated and will be removed in v.3.0.0. Please use the `retry_strategy` instead
  245 +- The `retry_max_delay` option is deprecated and will be removed in v.3.0.0. Please use the `retry_strategy` instead
  246 +- The drain event is deprecated and will be removed in v.3.0.0. Please listen to the stream drain event instead
  247 +- The idle event is deprecated and will likely be removed in v.3.0.0. If you rely on this feature please open a new ticket in node_redis with your use case
  248 +- Redis < v. 2.6 is not officially supported anymore and might not work in all cases. Please update to a newer redis version as it is not possible to test for these old versions
  249 +- Removed non documented command syntax (adding the callback to an arguments array instead of passing it as individual argument)
  250 +
  251 +## v.2.4.2 - 27 Nov, 2015
  252 +
  253 +Bugfixes
  254 +
  255 +- Fixed not emitting ready after reconnect with disable_resubscribing ([@maxgalbu](https://github.com/maxgalbu))
  256 +
  257 +## v.2.4.1 - 25 Nov, 2015
  258 +
  259 +Bugfixes
  260 +
  261 +- Fixed a js parser regression introduced in 2.4.0 ([@BridgeAR](https://github.com/BridgeAR))
  262 +
  263 +## v.2.4.0 - 25 Nov, 2015
  264 +
  265 +Features
  266 +
  267 +- Added `tls` option to initiate a connection to a redis server behind a TLS proxy. Thanks ([@paddybyers](https://github.com/paddybyers))
  268 +- Added `prefix` option to auto key prefix any command with the provided prefix ([@luin](https://github.com/luin) & [@BridgeAR](https://github.com/BridgeAR))
  269 +- Added `url` option to pass the connection url with the options object ([@BridgeAR](https://github.com/BridgeAR))
  270 +- Added `client.duplicate([options])` to duplicate the current client and return a new one with the same options ([@BridgeAR](https://github.com/BridgeAR))
  271 +- Improve performance by up to 20% on almost all use cases ([@BridgeAR](https://github.com/BridgeAR))
  272 +
  273 +Bugfixes
  274 +
  275 +- Fixed js parser handling big values slow ([@BridgeAR](https://github.com/BridgeAR))
  276 + - The speed is now on par with the hiredis parser.
  277 +
  278 +## v.2.3.1 - 18 Nov, 2015
  279 +
  280 +Bugfixes
  281 +
  282 +- Fixed saving buffers with charsets other than utf-8 while using multi ([@BridgeAR](https://github.com/BridgeAR))
  283 +- Fixed js parser handling big values very slow ([@BridgeAR](https://github.com/BridgeAR))
  284 + - The speed is up to ~500% faster than before but still up to ~50% slower than the hiredis parser.
  285 +
  286 +## v.2.3.0 - 30 Oct, 2015
  287 +
  288 +Features
  289 +
  290 +- Improve speed further for: ([@BridgeAR](https://github.com/BridgeAR))
  291 + - saving big strings (up to +300%)
  292 + - using .multi / .batch (up to +50% / on Node.js 0.10.x +300%)
  293 + - saving small buffers
  294 +- Increased coverage to 99% ([@BridgeAR](https://github.com/BridgeAR))
  295 +- Refactored manual backpressure control ([@BridgeAR](https://github.com/BridgeAR))
  296 + - Removed the high water mark and low water mark. Such a mechanism should be implemented by a user instead
  297 + - The `drain` event is from now on only emitted if the stream really had to buffer
  298 +- Reduced the default connect_timeout to be one hour instead of 24h ([@BridgeAR](https://github.com/BridgeAR))
  299 +- Added .path to redis.createClient(options); ([@BridgeAR](https://github.com/BridgeAR))
  300 +- Ignore info command, if not available on server ([@ivanB1975](https://github.com/ivanB1975))
  301 +
  302 +Bugfixes
  303 +
  304 +- Fixed a js parser error that could result in a timeout ([@BridgeAR](https://github.com/BridgeAR))
  305 +- Fixed .multi / .batch used with Node.js 0.10.x not working properly after a reconnect ([@BridgeAR](https://github.com/BridgeAR))
  306 +- Fixed fired but not yet returned commands not being rejected after a connection loss ([@BridgeAR](https://github.com/BridgeAR))
  307 +- Fixed connect_timeout not respected if no connection has ever been established ([@gagle](https://github.com/gagle) & [@benjie](https://github.com/benjie))
  308 +- Fixed return_buffers in pub sub mode ([@komachi](https://github.com/komachi))
  309 +
  310 +## v.2.2.5 - 18 Oct, 2015
  311 +
  312 +Bugfixes
  313 +
  314 +- Fixed undefined options passed to a new instance not accepted (possible with individual .createClient functions) ([@BridgeAR](https://github.com/BridgeAR))
  315 +
  316 +## v.2.2.4 - 17 Oct, 2015
  317 +
  318 +Bugfixes
  319 +
  320 +- Fixed unspecific error message for unresolvable commands ([@BridgeAR](https://github.com/BridgeAR))
  321 +- Fixed not allowed command error in pubsub mode not being returned in a provided callback ([@BridgeAR](https://github.com/BridgeAR))
  322 +- Fixed to many commands forbidden in pub sub mode ([@BridgeAR](https://github.com/BridgeAR))
  323 +- Fixed mutation of the arguments array passed to .multi / .batch constructor ([@BridgeAR](https://github.com/BridgeAR))
  324 +- Fixed mutation of the options object passed to createClient ([@BridgeAR](https://github.com/BridgeAR))
  325 +- Fixed error callback in .multi not called if connection in broken mode ([@BridgeAR](https://github.com/BridgeAR))
  326 +
  327 +## v.2.2.3 - 14 Oct, 2015
  328 +
  329 +Bugfixes
  330 +
  331 +- Fixed multi not being executed on Node 0.10.x if node_redis not yet ready ([@BridgeAR](https://github.com/BridgeAR))
  332 +
  333 +## v.2.2.2 - 14 Oct, 2015
  334 +
  335 +Bugfixes
  336 +
  337 +- Fixed regular commands not being executed after a .multi until .exec was called ([@BridgeAR](https://github.com/BridgeAR))
  338 +
  339 +## v.2.2.1 - 12 Oct, 2015
  340 +
  341 +No code change
  342 +
  343 +## v.2.2.0 - 12 Oct, 2015 - The peregrino falcon
  344 +
  345 +The peregrino falcon is the fasted bird on earth and this is what this release is all about: Increased performance for heavy usage by up to **400%** [sic!] and increased overall performance for any command as well. Please check the benchmarks in the [README.md](README.md) for further details.
  346 +
  347 +Features
  348 +
  349 +- Added rename_commands options to handle renamed commands from the redis config ([@digmxl](https://github.com/digmxl) & [@BridgeAR](https://github.com/BridgeAR))
  350 +- Added disable_resubscribing option to prevent a client from resubscribing after reconnecting ([@BridgeAR](https://github.com/BridgeAR))
  351 +- Increased performance ([@BridgeAR](https://github.com/BridgeAR))
  352 + - exchanging built in queue with [@petkaantonov](https://github.com/petkaantonov)'s [double-ended queue](https://github.com/petkaantonov/deque)
  353 + - prevent polymorphism
  354 + - optimize statements
  355 +- Added *.batch* command, similar to .multi but without transaction ([@BridgeAR](https://github.com/BridgeAR))
  356 +- Improved pipelining to minimize the [RTT](http://redis.io/topics/pipelining) further ([@BridgeAR](https://github.com/BridgeAR))
  357 +
  358 +Bugfixes
  359 +
  360 +- Fixed a javascript parser regression introduced in 2.0 that could result in timeouts on high load. ([@BridgeAR](https://github.com/BridgeAR))
  361 + - I was not able to write a regression test for this, since the error seems to only occur under heavy load with special conditions. So please have a look for timeouts with the js parser, if you use it and report all issues and switch to the hiredis parser in the meanwhile. If you're able to come up with a reproducable test case, this would be even better :)
  362 +- Fixed should_buffer boolean for .exec, .select and .auth commands not being returned and fix a couple special conditions ([@BridgeAR](https://github.com/BridgeAR))
  363 +
  364 +If you do not rely on transactions but want to reduce the RTT you can use .batch from now on. It'll behave just the same as .multi but it does not have any transaction and therefor won't roll back any failed commands.<br>
  365 +Both .multi and .batch are from now on going to cache the commands and release them while calling .exec.
  366 +
  367 +Please consider using .batch instead of looping through a lot of commands one by one. This will significantly improve your performance.
  368 +
  369 +Here are some stats compared to ioredis 1.9.1 (Lenovo T450s i7-5600U):
  370 +
  371 + simple set
  372 + 82,496 op/s » ioredis
  373 + 112,617 op/s » node_redis
  374 +
  375 + simple get
  376 + 82,015 op/s » ioredis
  377 + 105,701 op/s » node_redis
  378 +
  379 + simple get with pipeline
  380 + 10,233 op/s » ioredis
  381 + 26,541 op/s » node_redis (using .batch)
  382 +
  383 + lrange 100
  384 + 7,321 op/s » ioredis
  385 + 26,155 op/s » node_redis
  386 +
  387 + publish
  388 + 90,524 op/s » ioredis
  389 + 112,823 op/s » node_redis
  390 +
  391 + subscribe
  392 + 43,783 op/s » ioredis
  393 + 61,889 op/s » node_redis
  394 +
  395 +To conclude: we can proudly say that node_redis is very likely outperforming any other node redis client.
  396 +
  397 +Known issues
  398 +
  399 +- The pub sub system has some flaws and those will be addressed in the next minor release
  400 +
  401 +## v2.1.0 - Oct 02, 2015
  402 +
  403 +Features:
  404 +
  405 +- Addded optional flush parameter to `.end`. If set to true, commands fired after using .end are going to be rejected instead of being ignored. (@crispy1989)
  406 +- Addded: host and port can now be provided in a single options object. E.g. redis.createClient({ host: 'localhost', port: 1337, max_attempts: 5 }); (@BridgeAR)
  407 +- Speedup common cases (@BridgeAR)
  408 +
  409 +Bugfixes:
  410 +
  411 +- Fix argument mutation while using the array notation with the multi constructor (@BridgeAR)
  412 +- Fix multi.hmset key not being type converted if used with an object and key not being a string (@BridgeAR)
  413 +- Fix parser errors not being catched properly (@BridgeAR)
  414 +- Fix a crash that could occur if a redis server does not return the info command as usual #541 (@BridgeAR)
  415 +- Explicitly passing undefined as a callback statement will work again. E.g. client.publish('channel', 'message', undefined); (@BridgeAR)
  416 +
  417 +## v2.0.1 - Sep 24, 2015
  418 +
  419 +Bugfixes:
  420 +
  421 +- Fix argument mutation while using the array notation in combination with keys / callbacks ([#866](.)). (@BridgeAR)
  422 +
  423 +## v2.0.0 - Sep 21, 2015
  424 +
  425 +This is the biggest release that node_redis had since it was released in 2010. A long list of outstanding bugs has been fixed, so we are very happy to present you redis 2.0 and we highly recommend updating as soon as possible.
  426 +
  427 +# What's new in 2.0
  428 +
  429 +- Implemented a "connection is broken" mode if no connection could be established
  430 +- node_redis no longer throws under any circumstances, preventing it from terminating applications.
  431 +- Multi error handling is now working properly
  432 +- Consistent command behavior including multi
  433 +- Windows support
  434 +- Improved performance
  435 +- A lot of code cleanup
  436 +- Many bug fixes
  437 +- Better user support!
  438 +
  439 +## Features:
  440 +
  441 +- Added a "redis connection is broken" mode after reaching max connection attempts / exceeding connection timeout. (@BridgeAR)
  442 +- Added NODE_DEBUG=redis env to activate the debug_mode (@BridgeAR)
  443 +- Added a default connection timeout of 24h instead of never timing out as a default (@BridgeAR)
  444 +- Added: Network errors and other stream errors will from now on include the error code as `err.code` property (@BridgeAR)
  445 +- Added: Errors thrown by redis will now include the redis error code as `err.code` property. (@skeggse & @BridgeAR)
  446 +- Added: Errors thrown by node_redis will now include a `err.command` property for the command used (@BridgeAR)
  447 +- Added new commands and drop support for deprecated *substr* (@BridgeAR)
  448 +- Added new possibilities how to provide the command arguments (@BridgeAR)
  449 +- The entries in the keyspace of the server_info is now an object instead of a string. (@SinisterLight & @BridgeAR)
  450 +- Small speedup here and there (e.g. by not using .toLowerCase() anymore) (@BridgeAR)
  451 +- Full windows support (@bcoe)
  452 +- Increased coverage by 10% and add a lot of tests to make sure everything works as it should. We now reached 97% :-) (@BridgeAR)
  453 +- Remove dead code, clean up and refactor very old chunks (@BridgeAR)
  454 +- Don't flush the offline queue if reconnecting (@BridgeAR)
  455 +- Emit all errors insteaf of throwing sometimes and sometimes emitting them (@BridgeAR)
  456 +- *auth_pass* passwords are now checked to be a valid password (@jcppman & @BridgeAR)
  457 +
  458 +## Bug fixes:
  459 +
  460 +- Don't kill the app anymore by randomly throwing errors sync instead of emitting them (@BridgeAR)
  461 +- Don't catch user errors anymore occuring in callbacks (no try callback anymore & more fixes for the parser) (@BridgeAR)
  462 +- Early garbage collection of queued items (@dohse)
  463 +- Fix js parser returning errors as strings (@BridgeAR)
  464 +- Do not wrap errors into other errors (@BridgeAR)
  465 +- Authentication failures are now returned in the callback instead of being emitted (@BridgeAR)
  466 +- Fix a memory leak on reconnect (@rahar)
  467 +- Using `send_command` directly may now also be called without the args as stated in the [README.md](./README.md) (@BridgeAR)
  468 +- Fix the multi.exec error handling (@BridgeAR)
  469 +- Fix commands being inconsistent and behaving wrong (@BridgeAR)
  470 +- Channel names with spaces are now properly resubscribed after a reconnection (@pbihler)
  471 +- Do not try to reconnect after the connection timeout has been exceeded (@BridgeAR)
  472 +- Ensure the execution order is observed if using .eval (@BridgeAR)
  473 +- Fix commands not being rejected after calling .quit (@BridgeAR)
  474 +- Fix .auth calling the callback twice if already connected (@BridgeAR)
  475 +- Fix detect_buffers not working in pub sub mode and while monitoring (@BridgeAR)
  476 +- Fix channel names always being strings instead of buffers while return_buffers is true (@BridgeAR)
  477 +- Don't print any debug statements if not asked for (@BridgeAR)
  478 +- Fix a couple small other bugs
  479 +
  480 +## Breaking changes:
  481 +
  482 +1. redis.send_command commands have to be lower case from now on. This does only apply if you use `.send_command` directly instead of the convenient methods like `redis.command`.
  483 +2. Error messages have changed quite a bit. If you depend on a specific wording please check your application carfully.
  484 +3. Errors are from now on always either returned if a callback is present or emitted. They won't be thrown (neither sync, nor async).
  485 +4. The Multi error handling has changed a lot!
  486 + - All errors are from now on errors instead of strings (this only applied to the js parser).
  487 + - If an error occurs while queueing the commands an EXECABORT error will be returned including the failed commands as `.errors` property instead of an array with errors.
  488 + - If an error occurs while executing the commands and that command has a callback it'll return the error as first parameter (`err, undefined` instead of `null, undefined`).
  489 + - All the errors occuring while executing the commands will stay in the result value as error instance (if you used the js parser before they would have been strings). Be aware that the transaction won't be aborted if those error occurr!
  490 + - If `multi.exec` does not have a callback and an EXECABORT error occurrs, it'll emit that error instead.
  491 +5. If redis can't connect to your redis server it'll give up after a certain point of failures (either max connection attempts or connection timeout exceeded). If that is the case it'll emit an CONNECTION_BROKEN error. You'll have to initiate a new client to try again afterwards.
  492 +6. The offline queue is not flushed anymore on a reconnect. It'll stay until node_redis gives up trying to reach the server or until you close the connection.
  493 +7. Before this release node_redis catched user errors and threw them async back. This is not the case anymore! No user behavior of what so ever will be tracked or catched.
  494 +8. The keyspace of `redis.server_info` (db0...) is from now on an object instead of an string.
  495 +
  496 +NodeRedis also thanks @qdb, @tobek, @cvibhagool, @frewsxcv, @davidbanham, @serv, @vitaliylag, @chrishamant, @GamingCoder and all other contributors that I may have missed for their contributions!
  497 +
  498 +From now on we'll push new releases more frequently out and fix further long outstanding things and implement new features.
  499 +
  500 +<hr>
  501 +
  502 +## v1.0.0 - Aug 30, 2015
  503 +
  504 +* Huge issue and pull-request cleanup. Thanks Blain! (@blainsmith)
  505 +* [#658](https://github.com/NodeRedis/node_redis/pull/658) Client now parses URL-format connection strings (e.g., redis://foo:pass@127.0.0.1:8080) (@kuwabarahiroshi)
  506 +* [#749](https://github.com/NodeRedis/node_redis/pull/749) Fix reconnection bug when client is in monitoring mode (@danielbprice)
  507 +* [#786](https://github.com/NodeRedis/node_redis/pull/786) Refactor createClient. Fixes #651 (@BridgeAR)
  508 +* [#793](https://github.com/NodeRedis/node_redis/pull/793) Refactor tests and improve test coverage (@erinspice, @bcoe)
  509 +* [#733](https://github.com/NodeRedis/node_redis/pull/733) Fixes detect_buffers functionality in the context of exec. Fixes #732, #263 (@raydog)
  510 +* [#785](https://github.com/NodeRedis/node_redis/pull/785) Tiny speedup by using 'use strict' (@BridgeAR)
  511 +* Fix extraneous error output due to pubsub tests (Mikael Kohlmyr)
  512 +
  513 +## v0.12.1 - Aug 10, 2014
  514 +* Fix IPv6/IPv4 family selection in node 0.11+ (Various)
  515 +
  516 +## v0.12.0 - Aug 9, 2014
  517 +* Fix unix socket support (Jack Tang)
  518 +* Improve createClient argument handling (Jack Tang)
  519 +
  520 +## v0.11.0 - Jul 10, 2014
  521 +
  522 +* IPv6 Support. (Yann Stephan)
  523 +* Revert error emitting and go back to throwing errors. (Bryce Baril)
  524 +* Set socket_keepalive to prevent long-lived client timeouts. (mohit)
  525 +* Correctly reset retry timer. (ouotuo)
  526 +* Domains protection from bad user exit. (Jake Verbaten)
  527 +* Fix reconnection socket logic to prevent misqueued entries. (Iain Proctor)
  528 +
  529 +## v0.10.3 - May 22, 2014
  530 +
  531 +* Update command list to match Redis 2.8.9 (Charles Feng)
  532 +
  533 +## v0.10.2 - May 18, 2014
  534 +
  535 +* Better binary key handling for HGETALL. (Nick Apperson)
  536 +* Fix test not resetting `error` handler. (CrypticSwarm)
  537 +* Fix SELECT error semantics. (Bryan English)
  538 +
  539 +## v0.10.1 - February 17, 2014
  540 +
  541 +* Skip plucking redis version from the INFO stream if INFO results weren't provided. (Robert Sköld)
  542 +
  543 +## v0.10.0 - December 21, 2013
  544 +
  545 +* Instead of throwing errors asynchronously, emit errors on client. (Bryce Baril)
  546 +
  547 +## v0.9.2 - December 15, 2013
  548 +
  549 +* Regenerate commands for new 2.8.x Redis commands. (Marek Ventur)
  550 +* Correctly time reconnect counts when using 'auth'. (William Hockey)
  551 +
  552 +## v0.9.1 - November 23, 2013
  553 +
  554 +* Allow hmset to accept numeric keys. (Alex Stokes)
  555 +* Fix TypeError for multiple MULTI/EXEC errors. (Kwangsu Kim)
  556 +
  557 +## v0.9.0 - October 17, 2013
  558 +
  559 +* Domains support. (Forrest L Norvell)
  560 +
  561 +## v0.8.6 - October 2, 2013
  562 +
  563 +* If error is already an Error, don't wrap it in another Error. (Mathieu M-Gosselin)
  564 +* Fix retry delay logic (Ian Babrou)
  565 +* Return Errors instead of strings where Errors are expected (Ian Babrou)
  566 +* Add experimental `.unref()` method to RedisClient (Bryce Baril / Olivier Lalonde)
  567 +* Strengthen checking of reply to prevent conflating "message" or "pmessage" fields with pub_sub replies. (Bryce Baril)
  568 +
  569 +## v0.8.5 - September 26, 2013
  570 +
  571 +* Add `auth_pass` option to connect and immediately authenticate (Henrik Peinar)
  572 +
  573 +## v0.8.4 - June 24, 2013
  574 +
  575 +Many contributed features and fixes, including:
  576 +* Ignore password set if not needed. (jbergknoff)
  577 +* Improved compatibility with 0.10.X for tests and client.end() (Bryce Baril)
  578 +* Protect connection retries from application exceptions. (Amos Barreto)
  579 +* Better exception handling for Multi/Exec (Thanasis Polychronakis)
  580 +* Renamed pubsub mode to subscriber mode (Luke Plaster)
  581 +* Treat SREM like SADD when passed an array (Martin Ciparelli)
  582 +* Fix empty unsub/punsub TypeError (Jeff Barczewski)
  583 +* Only attempt to run a callback if it one was provided (jifeng)
  584 +
  585 +## v0.8.3 - April 09, 2013
  586 +
  587 +Many contributed features and fixes, including:
  588 +* Fix some tests for Node.js version 0.9.x+ changes (Roman Ivanilov)
  589 +* Fix error when commands submitted after idle event handler (roamm)
  590 +* Bypass Redis for no-op SET/SETEX commands (jifeng)
  591 +* Fix HMGET + detect_buffers (Joffrey F)
  592 +* Fix CLIENT LOAD functionality (Jonas Dohse)
  593 +* Add percentage outputs to diff_multi_bench_output.js (Bryce Baril)
  594 +* Add retry_max_delay option (Tomasz Durka)
  595 +* Fix parser off-by-one errors with nested multi-bulk replies (Bryce Baril)
  596 +* Prevent parser from sinking application-side exceptions (Bryce Baril)
  597 +* Fix parser incorrect buffer skip when parsing multi-bulk errors (Bryce Baril)
  598 +* Reverted previous change with throwing on non-string values with HMSET (David Trejo)
  599 +* Fix command queue sync issue when using pubsub (Tom Leach)
  600 +* Fix compatibility with two-word Redis commands (Jonas Dohse)
  601 +* Add EVAL with array syntax (dmoena)
  602 +* Fix tests due to Redis reply order changes in 2.6.5+ (Bryce Baril)
  603 +* Added a test for the SLOWLOG command (Nitesh Sinha)
  604 +* Fix SMEMBERS order dependency in test broken by Redis changes (Garrett Johnson)
  605 +* Update commands for new Redis commands (David Trejo)
  606 +* Prevent exception from SELECT on subscriber reconnection (roamm)
  607 +
  608 +
  609 +## v0.8.2 - November 11, 2012
  610 +
  611 +Another version bump because 0.8.1 didn't get applied properly for some mysterious reason.
  612 +Sorry about that.
  613 +
  614 +Changed name of "faster" parser to "javascript".
  615 +
  616 +## v0.8.1 - September 11, 2012
  617 +
  618 +Important bug fix for null responses (Jerry Sievert)
  619 +
  620 +## v0.8.0 - September 10, 2012
  621 +
  622 +Many contributed features and fixes, including:
  623 +
  624 +* Pure JavaScript reply parser that is usually faster than hiredis (Jerry Sievert)
  625 +* Remove hiredis as optionalDependency from package.json. It still works if you want it.
  626 +* Restore client state on reconnect, including select, subscribe, and monitor. (Ignacio Burgueño)
  627 +* Fix idle event (Trae Robrock)
  628 +* Many documentation improvements and bug fixes (David Trejo)
  629 +
  630 +## v0.7.2 - April 29, 2012
  631 +
  632 +Many contributed fixes. Thank you, contributors.
  633 +
  634 +* [GH-190] - pub/sub mode fix (Brian Noguchi)
  635 +* [GH-165] - parser selection fix (TEHEK)
  636 +* numerous documentation and examples updates
  637 +* auth errors emit Errors instead of Strings (David Trejo)
  638 +
  639 +## v0.7.1 - November 15, 2011
  640 +
  641 +Fix regression in reconnect logic.
  642 +
  643 +Very much need automated tests for reconnection and queue logic.
  644 +
  645 +## v0.7.0 - November 14, 2011
  646 +
  647 +Many contributed fixes. Thanks everybody.
  648 +
  649 +* [GH-127] - properly re-initialize parser on reconnect
  650 +* [GH-136] - handle passing undefined as callback (Ian Babrou)
  651 +* [GH-139] - properly handle exceptions thrown in pub/sub event handlers (Felix Geisendörfer)
  652 +* [GH-141] - detect closing state on stream error (Felix Geisendörfer)
  653 +* [GH-142] - re-select database on reconnection (Jean-Hugues Pinson)
  654 +* [GH-146] - add sort example (Maksim Lin)
  655 +
  656 +Some more goodies:
  657 +
  658 +* Fix bugs with node 0.6
  659 +* Performance improvements
  660 +* New version of `multi_bench.js` that tests more realistic scenarios
  661 +* [GH-140] - support optional callback for subscribe commands
  662 +* Properly flush and error out command queue when connection fails
  663 +* Initial work on reconnection thresholds
  664 +
  665 +## v0.6.7 - July 30, 2011
  666 +
  667 +(accidentally skipped v0.6.6)
  668 +
  669 +Fix and test for [GH-123]
  670 +
  671 +Passing an Array as as the last argument should expand as users
  672 +expect. The old behavior was to coerce the arguments into Strings,
  673 +which did surprising things with Arrays.
  674 +
  675 +## v0.6.5 - July 6, 2011
  676 +
  677 +Contributed changes:
  678 +
  679 +* Support SlowBuffers (Umair Siddique)
  680 +* Add Multi to exports (Louis-Philippe Perron)
  681 +* Fix for drain event calculation (Vladimir Dronnikov)
  682 +
  683 +Thanks!
  684 +
  685 +## v0.6.4 - June 30, 2011
  686 +
  687 +Fix bug with optional callbacks for hmset.
  688 +
  689 +## v0.6.2 - June 30, 2011
  690 +
  691 +Bugs fixed:
  692 +
  693 +* authentication retry while server is loading db (danmaz74) [GH-101]
  694 +* command arguments processing issue with arrays
  695 +
  696 +New features:
  697 +
  698 +* Auto update of new commands from redis.io (Dave Hoover)
  699 +* Performance improvements and backpressure controls.
  700 +* Commands now return the true/false value from the underlying socket write(s).
  701 +* Implement command_queue high water and low water for more better control of queueing.
  702 +
  703 +See `examples/backpressure_drain.js` for more information.
  704 +
  705 +## v0.6.1 - June 29, 2011
  706 +
  707 +Add support and tests for Redis scripting through EXEC command.
  708 +
  709 +Bug fix for monitor mode. (forddg)
  710 +
  711 +Auto update of new commands from redis.io (Dave Hoover)
  712 +
  713 +## v0.6.0 - April 21, 2011
  714 +
  715 +Lots of bugs fixed.
  716 +
  717 +* connection error did not properly trigger reconnection logic [GH-85]
  718 +* client.hmget(key, [val1, val2]) was not expanding properly [GH-66]
  719 +* client.quit() while in pub/sub mode would throw an error [GH-87]
  720 +* client.multi(['hmset', 'key', {foo: 'bar'}]) fails [GH-92]
  721 +* unsubscribe before subscribe would make things very confused [GH-88]
  722 +* Add BRPOPLPUSH [GH-79]
  723 +
  724 +## v0.5.11 - April 7, 2011
  725 +
  726 +Added DISCARD
  727 +
  728 +I originally didn't think DISCARD would do anything here because of the clever MULTI interface, but somebody
  729 +pointed out to me that DISCARD can be used to flush the WATCH set.
  730 +
  731 +## v0.5.10 - April 6, 2011
  732 +
  733 +Added HVALS
  734 +
  735 +## v0.5.9 - March 14, 2011
  736 +
  737 +Fix bug with empty Array arguments - Andy Ray
  738 +
  739 +## v0.5.8 - March 14, 2011
  740 +
  741 +Add `MONITOR` command and special monitor command reply parsing.
  742 +
  743 +## v0.5.7 - February 27, 2011
  744 +
  745 +Add magical auth command.
  746 +
  747 +Authentication is now remembered by the client and will be automatically sent to the server
  748 +on every connection, including any reconnections.
  749 +
  750 +## v0.5.6 - February 22, 2011
  751 +
  752 +Fix bug in ready check with `return_buffers` set to `true`.
  753 +
  754 +Thanks to Dean Mao and Austin Chau.
  755 +
  756 +## v0.5.5 - February 16, 2011
  757 +
  758 +Add probe for server readiness.
  759 +
  760 +When a Redis server starts up, it might take a while to load the dataset into memory.
  761 +During this time, the server will accept connections, but will return errors for all non-INFO
  762 +commands. Now node_redis will send an INFO command whenever it connects to a server.
  763 +If the info command indicates that the server is not ready, the client will keep trying until
  764 +the server is ready. Once it is ready, the client will emit a "ready" event as well as the
  765 +"connect" event. The client will queue up all commands sent before the server is ready, just
  766 +like it did before. When the server is ready, all offline/non-ready commands will be replayed.
  767 +This should be backward compatible with previous versions.
  768 +
  769 +To disable this ready check behavior, set `options.no_ready_check` when creating the client.
  770 +
  771 +As a side effect of this change, the key/val params from the info command are available as
  772 +`client.server_options`. Further, the version string is decomposed into individual elements
  773 +in `client.server_options.versions`.
  774 +
  775 +## v0.5.4 - February 11, 2011
  776 +
  777 +Fix excess memory consumption from Queue backing store.
  778 +
  779 +Thanks to Gustaf Sjöberg.
  780 +
  781 +## v0.5.3 - February 5, 2011
  782 +
  783 +Fix multi/exec error reply callback logic.
  784 +
  785 +Thanks to Stella Laurenzo.
  786 +
  787 +## v0.5.2 - January 18, 2011
  788 +
  789 +Fix bug where unhandled error replies confuse the parser.
  790 +
  791 +## v0.5.1 - January 18, 2011
  792 +
  793 +Fix bug where subscribe commands would not handle redis-server startup error properly.
  794 +
  795 +## v0.5.0 - December 29, 2010
  796 +
  797 +Some bug fixes:
  798 +
  799 +* An important bug fix in reconnection logic. Previously, reply callbacks would be invoked twice after
  800 + a reconnect.
  801 +* Changed error callback argument to be an actual Error object.
  802 +
  803 +New feature:
  804 +
  805 +* Add friendly syntax for HMSET using an object.
  806 +
  807 +## v0.4.1 - December 8, 2010
  808 +
  809 +Remove warning about missing hiredis. You probably do want it though.
  810 +
  811 +## v0.4.0 - December 5, 2010
  812 +
  813 +Support for multiple response parsers and hiredis C library from Pieter Noordhuis.
  814 +Return Strings instead of Buffers by default.
  815 +Empty nested mb reply bug fix.
  816 +
  817 +## v0.3.9 - November 30, 2010
  818 +
  819 +Fix parser bug on failed EXECs.
  820 +
  821 +## v0.3.8 - November 10, 2010
  822 +
  823 +Fix for null MULTI response when WATCH condition fails.
  824 +
  825 +## v0.3.7 - November 9, 2010
  826 +
  827 +Add "drain" and "idle" events.
  828 +
  829 +## v0.3.6 - November 3, 2010
  830 +
  831 +Add all known Redis commands from Redis master, even ones that are coming in 2.2 and beyond.
  832 +
  833 +Send a friendlier "error" event message on stream errors like connection refused / reset.
  834 +
  835 +## v0.3.5 - October 21, 2010
  836 +
  837 +A few bug fixes.
  838 +
  839 +* Fixed bug with `nil` multi-bulk reply lengths that showed up with `BLPOP` timeouts.
  840 +* Only emit `end` once when connection goes away.
  841 +* Fixed bug in `test.js` where driver finished before all tests completed.
  842 +
  843 +## unversioned wasteland
  844 +
  845 +See the git history for what happened before.
  1 +'use strict';
  2 +
  3 +var net = require('net');
  4 +var tls = require('tls');
  5 +var util = require('util');
  6 +var utils = require('./lib/utils');
  7 +var Command = require('./lib/command');
  8 +var Queue = require('double-ended-queue');
  9 +var errorClasses = require('./lib/customErrors');
  10 +var EventEmitter = require('events');
  11 +var Parser = require('redis-parser');
  12 +var commands = require('redis-commands');
  13 +var debug = require('./lib/debug');
  14 +var unifyOptions = require('./lib/createClient');
  15 +var SUBSCRIBE_COMMANDS = {
  16 + subscribe: true,
  17 + unsubscribe: true,
  18 + psubscribe: true,
  19 + punsubscribe: true
  20 +};
  21 +
  22 +// Newer Node.js versions > 0.10 return the EventEmitter right away and using .EventEmitter was deprecated
  23 +if (typeof EventEmitter !== 'function') {
  24 + EventEmitter = EventEmitter.EventEmitter;
  25 +}
  26 +
  27 +function noop () {}
  28 +
  29 +function handle_detect_buffers_reply (reply, command, buffer_args) {
  30 + if (buffer_args === false || this.message_buffers) {
  31 + // If detect_buffers option was specified, then the reply from the parser will be a buffer.
  32 + // If this command did not use Buffer arguments, then convert the reply to Strings here.
  33 + reply = utils.reply_to_strings(reply);
  34 + }
  35 +
  36 + if (command === 'hgetall') {
  37 + reply = utils.reply_to_object(reply);
  38 + }
  39 + return reply;
  40 +}
  41 +
  42 +exports.debug_mode = /\bredis\b/i.test(process.env.NODE_DEBUG);
  43 +
  44 +// Attention: The second parameter might be removed at will and is not officially supported.
  45 +// Do not rely on this
  46 +function RedisClient (options, stream) {
  47 + // Copy the options so they are not mutated
  48 + options = utils.clone(options);
  49 + EventEmitter.call(this);
  50 + var cnx_options = {};
  51 + var self = this;
  52 + /* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
  53 + for (var tls_option in options.tls) {
  54 + cnx_options[tls_option] = options.tls[tls_option];
  55 + // Copy the tls options into the general options to make sure the address is set right
  56 + if (tls_option === 'port' || tls_option === 'host' || tls_option === 'path' || tls_option === 'family') {
  57 + options[tls_option] = options.tls[tls_option];
  58 + }
  59 + }
  60 + if (stream) {
  61 + // The stream from the outside is used so no connection from this side is triggered but from the server this client should talk to
  62 + // Reconnect etc won't work with this. This requires monkey patching to work, so it is not officially supported
  63 + options.stream = stream;
  64 + this.address = '"Private stream"';
  65 + } else if (options.path) {
  66 + cnx_options.path = options.path;
  67 + this.address = options.path;
  68 + } else {
  69 + cnx_options.port = +options.port || 6379;
  70 + cnx_options.host = options.host || '127.0.0.1';
  71 + cnx_options.family = (!options.family && net.isIP(cnx_options.host)) || (options.family === 'IPv6' ? 6 : 4);
  72 + this.address = cnx_options.host + ':' + cnx_options.port;
  73 + }
  74 + // Warn on misusing deprecated functions
  75 + if (typeof options.retry_strategy === 'function') {
  76 + if ('max_attempts' in options) {
  77 + self.warn('WARNING: You activated the retry_strategy and max_attempts at the same time. This is not possible and max_attempts will be ignored.');
  78 + // Do not print deprecation warnings twice
  79 + delete options.max_attempts;
  80 + }
  81 + if ('retry_max_delay' in options) {
  82 + self.warn('WARNING: You activated the retry_strategy and retry_max_delay at the same time. This is not possible and retry_max_delay will be ignored.');
  83 + // Do not print deprecation warnings twice
  84 + delete options.retry_max_delay;
  85 + }
  86 + }
  87 +
  88 + this.connection_options = cnx_options;
  89 + this.connection_id = RedisClient.connection_id++;
  90 + this.connected = false;
  91 + this.ready = false;
  92 + if (options.socket_nodelay === undefined) {
  93 + options.socket_nodelay = true;
  94 + } else if (!options.socket_nodelay) { // Only warn users with this set to false
  95 + self.warn(
  96 + 'socket_nodelay is deprecated and will be removed in v.3.0.0.\n' +
  97 + 'Setting socket_nodelay to false likely results in a reduced throughput. Please use .batch for pipelining instead.\n' +
  98 + 'If you are sure you rely on the NAGLE-algorithm you can activate it by calling client.stream.setNoDelay(false) instead.'
  99 + );
  100 + }
  101 + if (options.socket_keepalive === undefined) {
  102 + options.socket_keepalive = true;
  103 + }
  104 + for (var command in options.rename_commands) {
  105 + options.rename_commands[command.toLowerCase()] = options.rename_commands[command];
  106 + }
  107 + options.return_buffers = !!options.return_buffers;
  108 + options.detect_buffers = !!options.detect_buffers;
  109 + // Override the detect_buffers setting if return_buffers is active and print a warning
  110 + if (options.return_buffers && options.detect_buffers) {
  111 + self.warn('WARNING: You activated return_buffers and detect_buffers at the same time. The return value is always going to be a buffer.');
  112 + options.detect_buffers = false;
  113 + }
  114 + if (options.detect_buffers) {
  115 + // We only need to look at the arguments if we do not know what we have to return
  116 + this.handle_reply = handle_detect_buffers_reply;
  117 + }
  118 + this.should_buffer = false;
  119 + this.max_attempts = options.max_attempts | 0;
  120 + if ('max_attempts' in options) {
  121 + self.warn(
  122 + 'max_attempts is deprecated and will be removed in v.3.0.0.\n' +
  123 + 'To reduce the number of options and to improve the reconnection handling please use the new `retry_strategy` option instead.\n' +
  124 + 'This replaces the max_attempts and retry_max_delay option.'
  125 + );
  126 + }
  127 + this.command_queue = new Queue(); // Holds sent commands to de-pipeline them
  128 + this.offline_queue = new Queue(); // Holds commands issued but not able to be sent
  129 + this.pipeline_queue = new Queue(); // Holds all pipelined commands
  130 + // ATTENTION: connect_timeout should change in v.3.0 so it does not count towards ending reconnection attempts after x seconds
  131 + // This should be done by the retry_strategy. Instead it should only be the timeout for connecting to redis
  132 + this.connect_timeout = +options.connect_timeout || 3600000; // 60 * 60 * 1000 ms
  133 + this.enable_offline_queue = options.enable_offline_queue === false ? false : true;
  134 + this.retry_max_delay = +options.retry_max_delay || null;
  135 + if ('retry_max_delay' in options) {
  136 + self.warn(
  137 + 'retry_max_delay is deprecated and will be removed in v.3.0.0.\n' +
  138 + 'To reduce the amount of options and the improve the reconnection handling please use the new `retry_strategy` option instead.\n' +
  139 + 'This replaces the max_attempts and retry_max_delay option.'
  140 + );
  141 + }
  142 + this.initialize_retry_vars();
  143 + this.pub_sub_mode = 0;
  144 + this.subscription_set = {};
  145 + this.monitoring = false;
  146 + this.message_buffers = false;
  147 + this.closing = false;
  148 + this.server_info = {};
  149 + this.auth_pass = options.auth_pass || options.password;
  150 + this.selected_db = options.db; // Save the selected db here, used when reconnecting
  151 + this.old_state = null;
  152 + this.fire_strings = true; // Determine if strings or buffers should be written to the stream
  153 + this.pipeline = false;
  154 + this.sub_commands_left = 0;
  155 + this.times_connected = 0;
  156 + this.buffers = options.return_buffers || options.detect_buffers;
  157 + this.options = options;
  158 + this.reply = 'ON'; // Returning replies is the default
  159 + this.create_stream();
  160 + // The listeners will not be attached right away, so let's print the deprecation message while the listener is attached
  161 + this.on('newListener', function (event) {
  162 + if (event === 'idle') {
  163 + this.warn(
  164 + 'The idle event listener is deprecated and will likely be removed in v.3.0.0.\n' +
  165 + 'If you rely on this feature please open a new ticket in node_redis with your use case'
  166 + );
  167 + } else if (event === 'drain') {
  168 + this.warn(
  169 + 'The drain event listener is deprecated and will be removed in v.3.0.0.\n' +
  170 + 'If you want to keep on listening to this event please listen to the stream drain event directly.'
  171 + );
  172 + } else if ((event === 'message_buffer' || event === 'pmessage_buffer' || event === 'messageBuffer' || event === 'pmessageBuffer') && !this.buffers && !this.message_buffers) {
  173 + if (this.reply_parser.name !== 'javascript') {
  174 + return this.warn(
  175 + 'You attached the "' + event + '" listener without the returnBuffers option set to true.\n' +
  176 + 'Please use the JavaScript parser or set the returnBuffers option to true to return buffers.'
  177 + );
  178 + }
  179 + this.reply_parser.optionReturnBuffers = true;
  180 + this.message_buffers = true;
  181 + this.handle_reply = handle_detect_buffers_reply;
  182 + }
  183 + });
  184 +}
  185 +util.inherits(RedisClient, EventEmitter);
  186 +
  187 +RedisClient.connection_id = 0;
  188 +
  189 +function create_parser (self) {
  190 + return new Parser({
  191 + returnReply: function (data) {
  192 + self.return_reply(data);
  193 + },
  194 + returnError: function (err) {
  195 + // Return a ReplyError to indicate Redis returned an error
  196 + self.return_error(err);
  197 + },
  198 + returnFatalError: function (err) {
  199 + // Error out all fired commands. Otherwise they might rely on faulty data. We have to reconnect to get in a working state again
  200 + // Note: the execution order is important. First flush and emit, then create the stream
  201 + err.message += '. Please report this.';
  202 + self.ready = false;
  203 + self.flush_and_error({
  204 + message: 'Fatal error encountert. Command aborted.',
  205 + code: 'NR_FATAL'
  206 + }, {
  207 + error: err,
  208 + queues: ['command_queue']
  209 + });
  210 + self.emit('error', err);
  211 + self.create_stream();
  212 + },
  213 + returnBuffers: self.buffers || self.message_buffers,
  214 + name: self.options.parser || 'javascript',
  215 + stringNumbers: self.options.string_numbers || false
  216 + });
  217 +}
  218 +
  219 +/******************************************************************************
  220 +
  221 + All functions in here are internal besides the RedisClient constructor
  222 + and the exported functions. Don't rely on them as they will be private
  223 + functions in node_redis v.3
  224 +
  225 +******************************************************************************/
  226 +
  227 +// Attention: the function name "create_stream" should not be changed, as other libraries need this to mock the stream (e.g. fakeredis)
  228 +RedisClient.prototype.create_stream = function () {
  229 + var self = this;
  230 +
  231 + // Init parser
  232 + this.reply_parser = create_parser(this);
  233 +
  234 + if (this.options.stream) {
  235 + // Only add the listeners once in case of a reconnect try (that won't work)
  236 + if (this.stream) {
  237 + return;
  238 + }
  239 + this.stream = this.options.stream;
  240 + } else {
  241 + // On a reconnect destroy the former stream and retry
  242 + if (this.stream) {
  243 + this.stream.removeAllListeners();
  244 + this.stream.destroy();
  245 + }
  246 +
  247 + /* istanbul ignore if: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
  248 + if (this.options.tls) {
  249 + this.stream = tls.connect(this.connection_options);
  250 + } else {
  251 + this.stream = net.createConnection(this.connection_options);
  252 + }
  253 + }
  254 +
  255 + if (this.options.connect_timeout) {
  256 + this.stream.setTimeout(this.connect_timeout, function () {
  257 + // Note: This is only tested if a internet connection is established
  258 + self.retry_totaltime = self.connect_timeout;
  259 + self.connection_gone('timeout');
  260 + });
  261 + }
  262 +
  263 + /* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
  264 + var connect_event = this.options.tls ? 'secureConnect' : 'connect';
  265 + this.stream.once(connect_event, function () {
  266 + this.removeAllListeners('timeout');
  267 + self.times_connected++;
  268 + self.on_connect();
  269 + });
  270 +
  271 + this.stream.on('data', function (buffer_from_socket) {
  272 + // The buffer_from_socket.toString() has a significant impact on big chunks and therefore this should only be used if necessary
  273 + debug('Net read ' + self.address + ' id ' + self.connection_id); // + ': ' + buffer_from_socket.toString());
  274 + self.reply_parser.execute(buffer_from_socket);
  275 + self.emit_idle();
  276 + });
  277 +
  278 + this.stream.on('error', function (err) {
  279 + self.on_error(err);
  280 + });
  281 +
  282 + /* istanbul ignore next: difficult to test and not important as long as we keep this listener */
  283 + this.stream.on('clientError', function (err) {
  284 + debug('clientError occured');
  285 + self.on_error(err);
  286 + });
  287 +
  288 + this.stream.once('close', function (hadError) {
  289 + self.connection_gone('close');
  290 + });
  291 +
  292 + this.stream.once('end', function () {
  293 + self.connection_gone('end');
  294 + });
  295 +
  296 + this.stream.on('drain', function () {
  297 + self.drain();
  298 + });
  299 +
  300 + if (this.options.socket_nodelay) {
  301 + this.stream.setNoDelay();
  302 + }
  303 +
  304 + // Fire the command before redis is connected to be sure it's the first fired command
  305 + if (this.auth_pass !== undefined) {
  306 + this.ready = true;
  307 + // Fail silently as we might not be able to connect
  308 + this.auth(this.auth_pass, function (err) {
  309 + if (err && err.code !== 'UNCERTAIN_STATE') {
  310 + self.emit('error', err);
  311 + }
  312 + });
  313 + this.ready = false;
  314 + }
  315 +};
  316 +
  317 +RedisClient.prototype.handle_reply = function (reply, command) {
  318 + if (command === 'hgetall') {
  319 + reply = utils.reply_to_object(reply);
  320 + }
  321 + return reply;
  322 +};
  323 +
  324 +RedisClient.prototype.cork = noop;
  325 +RedisClient.prototype.uncork = noop;
  326 +
  327 +RedisClient.prototype.initialize_retry_vars = function () {
  328 + this.retry_timer = null;
  329 + this.retry_totaltime = 0;
  330 + this.retry_delay = 200;
  331 + this.retry_backoff = 1.7;
  332 + this.attempts = 1;
  333 +};
  334 +
  335 +RedisClient.prototype.warn = function (msg) {
  336 + var self = this;
  337 + // Warn on the next tick. Otherwise no event listener can be added
  338 + // for warnings that are emitted in the redis client constructor
  339 + process.nextTick(function () {
  340 + if (self.listeners('warning').length !== 0) {
  341 + self.emit('warning', msg);
  342 + } else {
  343 + console.warn('node_redis:', msg);
  344 + }
  345 + });
  346 +};
  347 +
  348 +// Flush provided queues, erroring any items with a callback first
  349 +RedisClient.prototype.flush_and_error = function (error_attributes, options) {
  350 + options = options || {};
  351 + var aggregated_errors = [];
  352 + var queue_names = options.queues || ['command_queue', 'offline_queue']; // Flush the command_queue first to keep the order intakt
  353 + for (var i = 0; i < queue_names.length; i++) {
  354 + // If the command was fired it might have been processed so far
  355 + if (queue_names[i] === 'command_queue') {
  356 + error_attributes.message += ' It might have been processed.';
  357 + } else { // As the command_queue is flushed first, remove this for the offline queue
  358 + error_attributes.message = error_attributes.message.replace(' It might have been processed.', '');
  359 + }
  360 + // Don't flush everything from the queue
  361 + for (var command_obj = this[queue_names[i]].shift(); command_obj; command_obj = this[queue_names[i]].shift()) {
  362 + var err = new errorClasses.AbortError(error_attributes);
  363 + if (command_obj.error) {
  364 + err.stack = err.stack + command_obj.error.stack.replace(/^Error.*?\n/, '\n');
  365 + }
  366 + err.command = command_obj.command.toUpperCase();
  367 + if (command_obj.args && command_obj.args.length) {
  368 + err.args = command_obj.args;
  369 + }
  370 + if (options.error) {
  371 + err.origin = options.error;
  372 + }
  373 + if (typeof command_obj.callback === 'function') {
  374 + command_obj.callback(err);
  375 + } else {
  376 + aggregated_errors.push(err);
  377 + }
  378 + }
  379 + }
  380 + // Currently this would be a breaking change, therefore it's only emitted in debug_mode
  381 + if (exports.debug_mode && aggregated_errors.length) {
  382 + var error;
  383 + if (aggregated_errors.length === 1) {
  384 + error = aggregated_errors[0];
  385 + } else {
  386 + error_attributes.message = error_attributes.message.replace('It', 'They').replace(/command/i, '$&s');
  387 + error = new errorClasses.AggregateError(error_attributes);
  388 + error.errors = aggregated_errors;
  389 + }
  390 + this.emit('error', error);
  391 + }
  392 +};
  393 +
  394 +RedisClient.prototype.on_error = function (err) {
  395 + if (this.closing) {
  396 + return;
  397 + }
  398 +
  399 + err.message = 'Redis connection to ' + this.address + ' failed - ' + err.message;
  400 + debug(err.message);
  401 + this.connected = false;
  402 + this.ready = false;
  403 +
  404 + // Only emit the error if the retry_stategy option is not set
  405 + if (!this.options.retry_strategy) {
  406 + this.emit('error', err);
  407 + }
  408 + // 'error' events get turned into exceptions if they aren't listened for. If the user handled this error
  409 + // then we should try to reconnect.
  410 + this.connection_gone('error', err);
  411 +};
  412 +
  413 +RedisClient.prototype.on_connect = function () {
  414 + debug('Stream connected ' + this.address + ' id ' + this.connection_id);
  415 +
  416 + this.connected = true;
  417 + this.ready = false;
  418 + this.emitted_end = false;
  419 + this.stream.setKeepAlive(this.options.socket_keepalive);
  420 + this.stream.setTimeout(0);
  421 +
  422 + this.emit('connect');
  423 + this.initialize_retry_vars();
  424 +
  425 + if (this.options.no_ready_check) {
  426 + this.on_ready();
  427 + } else {
  428 + this.ready_check();
  429 + }
  430 +};
  431 +
  432 +RedisClient.prototype.on_ready = function () {
  433 + var self = this;
  434 +
  435 + debug('on_ready called ' + this.address + ' id ' + this.connection_id);
  436 + this.ready = true;
  437 +
  438 + this.cork = function () {
  439 + self.pipeline = true;
  440 + if (self.stream.cork) {
  441 + self.stream.cork();
  442 + }
  443 + };
  444 + this.uncork = function () {
  445 + if (self.fire_strings) {
  446 + self.write_strings();
  447 + } else {
  448 + self.write_buffers();
  449 + }
  450 + self.pipeline = false;
  451 + self.fire_strings = true;
  452 + if (self.stream.uncork) {
  453 + // TODO: Consider using next tick here. See https://github.com/NodeRedis/node_redis/issues/1033
  454 + self.stream.uncork();
  455 + }
  456 + };
  457 +
  458 + // Restore modal commands from previous connection. The order of the commands is important
  459 + if (this.selected_db !== undefined) {
  460 + this.internal_send_command(new Command('select', [this.selected_db]));
  461 + }
  462 + if (this.monitoring) { // Monitor has to be fired before pub sub commands
  463 + this.internal_send_command(new Command('monitor', []));
  464 + }
  465 + var callback_count = Object.keys(this.subscription_set).length;
  466 + if (!this.options.disable_resubscribing && callback_count) {
  467 + // only emit 'ready' when all subscriptions were made again
  468 + // TODO: Remove the countdown for ready here. This is not coherent with all other modes and should therefore not be handled special
  469 + // We know we are ready as soon as all commands were fired
  470 + var callback = function () {
  471 + callback_count--;
  472 + if (callback_count === 0) {
  473 + self.emit('ready');
  474 + }
  475 + };
  476 + debug('Sending pub/sub on_ready commands');
  477 + for (var key in this.subscription_set) {
  478 + var command = key.slice(0, key.indexOf('_'));
  479 + var args = this.subscription_set[key];
  480 + this[command]([args], callback);
  481 + }
  482 + this.send_offline_queue();
  483 + return;
  484 + }
  485 + this.send_offline_queue();
  486 + this.emit('ready');
  487 +};
  488 +
  489 +RedisClient.prototype.on_info_cmd = function (err, res) {
  490 + if (err) {
  491 + if (err.message === "ERR unknown command 'info'") {
  492 + this.on_ready();
  493 + return;
  494 + }
  495 + err.message = 'Ready check failed: ' + err.message;
  496 + this.emit('error', err);
  497 + return;
  498 + }
  499 +
  500 + /* istanbul ignore if: some servers might not respond with any info data. This is just a safety check that is difficult to test */
  501 + if (!res) {
  502 + debug('The info command returned without any data.');
  503 + this.on_ready();
  504 + return;
  505 + }
  506 +
  507 + if (!this.server_info.loading || this.server_info.loading === '0') {
  508 + // If the master_link_status exists but the link is not up, try again after 50 ms
  509 + if (this.server_info.master_link_status && this.server_info.master_link_status !== 'up') {
  510 + this.server_info.loading_eta_seconds = 0.05;
  511 + } else {
  512 + // Eta loading should change
  513 + debug('Redis server ready.');
  514 + this.on_ready();
  515 + return;
  516 + }
  517 + }
  518 +
  519 + var retry_time = +this.server_info.loading_eta_seconds * 1000;
  520 + if (retry_time > 1000) {
  521 + retry_time = 1000;
  522 + }
  523 + debug('Redis server still loading, trying again in ' + retry_time);
  524 + setTimeout(function (self) {
  525 + self.ready_check();
  526 + }, retry_time, this);
  527 +};
  528 +
  529 +RedisClient.prototype.ready_check = function () {
  530 + var self = this;
  531 + debug('Checking server ready state...');
  532 + // Always fire this info command as first command even if other commands are already queued up
  533 + this.ready = true;
  534 + this.info(function (err, res) {
  535 + self.on_info_cmd(err, res);
  536 + });
  537 + this.ready = false;
  538 +};
  539 +
  540 +RedisClient.prototype.send_offline_queue = function () {
  541 + for (var command_obj = this.offline_queue.shift(); command_obj; command_obj = this.offline_queue.shift()) {
  542 + debug('Sending offline command: ' + command_obj.command);
  543 + this.internal_send_command(command_obj);
  544 + }
  545 + this.drain();
  546 +};
  547 +
  548 +var retry_connection = function (self, error) {
  549 + debug('Retrying connection...');
  550 +
  551 + var reconnect_params = {
  552 + delay: self.retry_delay,
  553 + attempt: self.attempts,
  554 + error: error
  555 + };
  556 + if (self.options.camel_case) {
  557 + reconnect_params.totalRetryTime = self.retry_totaltime;
  558 + reconnect_params.timesConnected = self.times_connected;
  559 + } else {
  560 + reconnect_params.total_retry_time = self.retry_totaltime;
  561 + reconnect_params.times_connected = self.times_connected;
  562 + }
  563 + self.emit('reconnecting', reconnect_params);
  564 +
  565 + self.retry_totaltime += self.retry_delay;
  566 + self.attempts += 1;
  567 + self.retry_delay = Math.round(self.retry_delay * self.retry_backoff);
  568 + self.create_stream();
  569 + self.retry_timer = null;
  570 +};
  571 +
  572 +RedisClient.prototype.connection_gone = function (why, error) {
  573 + // If a retry is already in progress, just let that happen
  574 + if (this.retry_timer) {
  575 + return;
  576 + }
  577 + error = error || null;
  578 +
  579 + debug('Redis connection is gone from ' + why + ' event.');
  580 + this.connected = false;
  581 + this.ready = false;
  582 + // Deactivate cork to work with the offline queue
  583 + this.cork = noop;
  584 + this.uncork = noop;
  585 + this.pipeline = false;
  586 + this.pub_sub_mode = 0;
  587 +
  588 + // since we are collapsing end and close, users don't expect to be called twice
  589 + if (!this.emitted_end) {
  590 + this.emit('end');
  591 + this.emitted_end = true;
  592 + }
  593 +
  594 + // If this is a requested shutdown, then don't retry
  595 + if (this.closing) {
  596 + debug('Connection ended by quit / end command, not retrying.');
  597 + this.flush_and_error({
  598 + message: 'Stream connection ended and command aborted.',
  599 + code: 'NR_CLOSED'
  600 + }, {
  601 + error: error
  602 + });
  603 + return;
  604 + }
  605 +
  606 + if (typeof this.options.retry_strategy === 'function') {
  607 + var retry_params = {
  608 + attempt: this.attempts,
  609 + error: error
  610 + };
  611 + if (this.options.camel_case) {
  612 + retry_params.totalRetryTime = this.retry_totaltime;
  613 + retry_params.timesConnected = this.times_connected;
  614 + } else {
  615 + retry_params.total_retry_time = this.retry_totaltime;
  616 + retry_params.times_connected = this.times_connected;
  617 + }
  618 + this.retry_delay = this.options.retry_strategy(retry_params);
  619 + if (typeof this.retry_delay !== 'number') {
  620 + // Pass individual error through
  621 + if (this.retry_delay instanceof Error) {
  622 + error = this.retry_delay;
  623 + }
  624 + this.flush_and_error({
  625 + message: 'Stream connection ended and command aborted.',
  626 + code: 'NR_CLOSED'
  627 + }, {
  628 + error: error
  629 + });
  630 + this.end(false);
  631 + return;
  632 + }
  633 + }
  634 +
  635 + if (this.max_attempts !== 0 && this.attempts >= this.max_attempts || this.retry_totaltime >= this.connect_timeout) {
  636 + var message = 'Redis connection in broken state: ';
  637 + if (this.retry_totaltime >= this.connect_timeout) {
  638 + message += 'connection timeout exceeded.';
  639 + } else {
  640 + message += 'maximum connection attempts exceeded.';
  641 + }
  642 +
  643 + this.flush_and_error({
  644 + message: message,
  645 + code: 'CONNECTION_BROKEN',
  646 + }, {
  647 + error: error
  648 + });
  649 + var err = new Error(message);
  650 + err.code = 'CONNECTION_BROKEN';
  651 + if (error) {
  652 + err.origin = error;
  653 + }
  654 + this.emit('error', err);
  655 + this.end(false);
  656 + return;
  657 + }
  658 +
  659 + // Retry commands after a reconnect instead of throwing an error. Use this with caution
  660 + if (this.options.retry_unfulfilled_commands) {
  661 + this.offline_queue.unshift.apply(this.offline_queue, this.command_queue.toArray());
  662 + this.command_queue.clear();
  663 + } else if (this.command_queue.length !== 0) {
  664 + this.flush_and_error({
  665 + message: 'Redis connection lost and command aborted.',
  666 + code: 'UNCERTAIN_STATE'
  667 + }, {
  668 + error: error,
  669 + queues: ['command_queue']
  670 + });
  671 + }
  672 +
  673 + if (this.retry_max_delay !== null && this.retry_delay > this.retry_max_delay) {
  674 + this.retry_delay = this.retry_max_delay;
  675 + } else if (this.retry_totaltime + this.retry_delay > this.connect_timeout) {
  676 + // Do not exceed the maximum
  677 + this.retry_delay = this.connect_timeout - this.retry_totaltime;
  678 + }
  679 +
  680 + debug('Retry connection in ' + this.retry_delay + ' ms');
  681 +
  682 + this.retry_timer = setTimeout(retry_connection, this.retry_delay, this, error);
  683 +};
  684 +
  685 +RedisClient.prototype.return_error = function (err) {
  686 + var command_obj = this.command_queue.shift();
  687 + if (command_obj.error) {
  688 + err.stack = command_obj.error.stack.replace(/^Error.*?\n/, 'ReplyError: ' + err.message + '\n');
  689 + }
  690 + err.command = command_obj.command.toUpperCase();
  691 + if (command_obj.args && command_obj.args.length) {
  692 + err.args = command_obj.args;
  693 + }
  694 +
  695 + // Count down pub sub mode if in entering modus
  696 + if (this.pub_sub_mode > 1) {
  697 + this.pub_sub_mode--;
  698 + }
  699 +
  700 + var match = err.message.match(utils.err_code);
  701 + // LUA script could return user errors that don't behave like all other errors!
  702 + if (match) {
  703 + err.code = match[1];
  704 + }
  705 +
  706 + utils.callback_or_emit(this, command_obj.callback, err);
  707 +};
  708 +
  709 +RedisClient.prototype.drain = function () {
  710 + this.emit('drain');
  711 + this.should_buffer = false;
  712 +};
  713 +
  714 +RedisClient.prototype.emit_idle = function () {
  715 + if (this.command_queue.length === 0 && this.pub_sub_mode === 0) {
  716 + this.emit('idle');
  717 + }
  718 +};
  719 +
  720 +function normal_reply (self, reply) {
  721 + var command_obj = self.command_queue.shift();
  722 + if (typeof command_obj.callback === 'function') {
  723 + if (command_obj.command !== 'exec') {
  724 + reply = self.handle_reply(reply, command_obj.command, command_obj.buffer_args);
  725 + }
  726 + command_obj.callback(null, reply);
  727 + } else {
  728 + debug('No callback for reply');
  729 + }
  730 +}
  731 +
  732 +function subscribe_unsubscribe (self, reply, type) {
  733 + // Subscribe commands take an optional callback and also emit an event, but only the _last_ response is included in the callback
  734 + // The pub sub commands return each argument in a separate return value and have to be handled that way
  735 + var command_obj = self.command_queue.get(0);
  736 + var buffer = self.options.return_buffers || self.options.detect_buffers && command_obj.buffer_args;
  737 + var channel = (buffer || reply[1] === null) ? reply[1] : reply[1].toString();
  738 + var count = +reply[2]; // Return the channel counter as number no matter if `string_numbers` is activated or not
  739 + debug(type, channel);
  740 +
  741 + // Emit first, then return the callback
  742 + if (channel !== null) { // Do not emit or "unsubscribe" something if there was no channel to unsubscribe from
  743 + self.emit(type, channel, count);
  744 + if (type === 'subscribe' || type === 'psubscribe') {
  745 + self.subscription_set[type + '_' + channel] = channel;
  746 + } else {
  747 + type = type === 'unsubscribe' ? 'subscribe' : 'psubscribe'; // Make types consistent
  748 + delete self.subscription_set[type + '_' + channel];
  749 + }
  750 + }
  751 +
  752 + if (command_obj.args.length === 1 || self.sub_commands_left === 1 || command_obj.args.length === 0 && (count === 0 || channel === null)) {
  753 + if (count === 0) { // unsubscribed from all channels
  754 + var running_command;
  755 + var i = 1;
  756 + self.pub_sub_mode = 0; // Deactivating pub sub mode
  757 + // This should be a rare case and therefore handling it this way should be good performance wise for the general case
  758 + while (running_command = self.command_queue.get(i)) {
  759 + if (SUBSCRIBE_COMMANDS[running_command.command]) {
  760 + self.pub_sub_mode = i; // Entering pub sub mode again
  761 + break;
  762 + }
  763 + i++;
  764 + }
  765 + }
  766 + self.command_queue.shift();
  767 + if (typeof command_obj.callback === 'function') {
  768 + // TODO: The current return value is pretty useless.
  769 + // Evaluate to change this in v.3 to return all subscribed / unsubscribed channels in an array including the number of channels subscribed too
  770 + command_obj.callback(null, channel);
  771 + }
  772 + self.sub_commands_left = 0;
  773 + } else {
  774 + if (self.sub_commands_left !== 0) {
  775 + self.sub_commands_left--;
  776 + } else {
  777 + self.sub_commands_left = command_obj.args.length ? command_obj.args.length - 1 : count;
  778 + }
  779 + }
  780 +}
  781 +
  782 +function return_pub_sub (self, reply) {
  783 + var type = reply[0].toString();
  784 + if (type === 'message') { // channel, message
  785 + if (!self.options.return_buffers || self.message_buffers) { // backwards compatible. Refactor this in v.3 to always return a string on the normal emitter
  786 + self.emit('message', reply[1].toString(), reply[2].toString());
  787 + self.emit('message_buffer', reply[1], reply[2]);
  788 + self.emit('messageBuffer', reply[1], reply[2]);
  789 + } else {
  790 + self.emit('message', reply[1], reply[2]);
  791 + }
  792 + } else if (type === 'pmessage') { // pattern, channel, message
  793 + if (!self.options.return_buffers || self.message_buffers) { // backwards compatible. Refactor this in v.3 to always return a string on the normal emitter
  794 + self.emit('pmessage', reply[1].toString(), reply[2].toString(), reply[3].toString());
  795 + self.emit('pmessage_buffer', reply[1], reply[2], reply[3]);
  796 + self.emit('pmessageBuffer', reply[1], reply[2], reply[3]);
  797 + } else {
  798 + self.emit('pmessage', reply[1], reply[2], reply[3]);
  799 + }
  800 + } else {
  801 + subscribe_unsubscribe(self, reply, type);
  802 + }
  803 +}
  804 +
  805 +RedisClient.prototype.return_reply = function (reply) {
  806 + if (this.monitoring) {
  807 + var replyStr;
  808 + if (this.buffers && Buffer.isBuffer(reply)) {
  809 + replyStr = reply.toString();
  810 + } else {
  811 + replyStr = reply;
  812 + }
  813 + // If in monitor mode, all normal commands are still working and we only want to emit the streamlined commands
  814 + if (typeof replyStr === 'string' && utils.monitor_regex.test(replyStr)) {
  815 + var timestamp = replyStr.slice(0, replyStr.indexOf(' '));
  816 + var args = replyStr.slice(replyStr.indexOf('"') + 1, -1).split('" "').map(function (elem) {
  817 + return elem.replace(/\\"/g, '"');
  818 + });
  819 + this.emit('monitor', timestamp, args, replyStr);
  820 + return;
  821 + }
  822 + }
  823 + if (this.pub_sub_mode === 0) {
  824 + normal_reply(this, reply);
  825 + } else if (this.pub_sub_mode !== 1) {
  826 + this.pub_sub_mode--;
  827 + normal_reply(this, reply);
  828 + } else if (!(reply instanceof Array) || reply.length <= 2) {
  829 + // Only PING and QUIT are allowed in this context besides the pub sub commands
  830 + // Ping replies with ['pong', null|value] and quit with 'OK'
  831 + normal_reply(this, reply);
  832 + } else {
  833 + return_pub_sub(this, reply);
  834 + }
  835 +};
  836 +
  837 +function handle_offline_command (self, command_obj) {
  838 + var command = command_obj.command;
  839 + var err, msg;
  840 + if (self.closing || !self.enable_offline_queue) {
  841 + command = command.toUpperCase();
  842 + if (!self.closing) {
  843 + if (self.stream.writable) {
  844 + msg = 'The connection is not yet established and the offline queue is deactivated.';
  845 + } else {
  846 + msg = 'Stream not writeable.';
  847 + }
  848 + } else {
  849 + msg = 'The connection is already closed.';
  850 + }
  851 + err = new errorClasses.AbortError({
  852 + message: command + " can't be processed. " + msg,
  853 + code: 'NR_CLOSED',
  854 + command: command
  855 + });
  856 + if (command_obj.args.length) {
  857 + err.args = command_obj.args;
  858 + }
  859 + utils.reply_in_order(self, command_obj.callback, err);
  860 + } else {
  861 + debug('Queueing ' + command + ' for next server connection.');
  862 + self.offline_queue.push(command_obj);
  863 + }
  864 + self.should_buffer = true;
  865 +}
  866 +
  867 +// Do not call internal_send_command directly, if you are not absolutly certain it handles everything properly
  868 +// e.g. monitor / info does not work with internal_send_command only
  869 +RedisClient.prototype.internal_send_command = function (command_obj) {
  870 + var arg, prefix_keys;
  871 + var i = 0;
  872 + var command_str = '';
  873 + var args = command_obj.args;
  874 + var command = command_obj.command;
  875 + var len = args.length;
  876 + var big_data = false;
  877 + var args_copy = new Array(len);
  878 +
  879 + if (process.domain && command_obj.callback) {
  880 + command_obj.callback = process.domain.bind(command_obj.callback);
  881 + }
  882 +
  883 + if (this.ready === false || this.stream.writable === false) {
  884 + // Handle offline commands right away
  885 + handle_offline_command(this, command_obj);
  886 + return false; // Indicate buffering
  887 + }
  888 +
  889 + for (i = 0; i < len; i += 1) {
  890 + if (typeof args[i] === 'string') {
  891 + // 30000 seemed to be a good value to switch to buffers after testing and checking the pros and cons
  892 + if (args[i].length > 30000) {
  893 + big_data = true;
  894 + args_copy[i] = new Buffer(args[i], 'utf8');
  895 + } else {
  896 + args_copy[i] = args[i];
  897 + }
  898 + } else if (typeof args[i] === 'object') { // Checking for object instead of Buffer.isBuffer helps us finding data types that we can't handle properly
  899 + if (args[i] instanceof Date) { // Accept dates as valid input
  900 + args_copy[i] = args[i].toString();
  901 + } else if (args[i] === null) {
  902 + this.warn(
  903 + 'Deprecated: The ' + command.toUpperCase() + ' command contains a "null" argument.\n' +
  904 + 'This is converted to a "null" string now and will return an error from v.3.0 on.\n' +
  905 + 'Please handle this in your code to make sure everything works as you intended it to.'
  906 + );
  907 + args_copy[i] = 'null'; // Backwards compatible :/
  908 + } else if (Buffer.isBuffer(args[i])) {
  909 + args_copy[i] = args[i];
  910 + command_obj.buffer_args = true;
  911 + big_data = true;
  912 + } else {
  913 + this.warn(
  914 + 'Deprecated: The ' + command.toUpperCase() + ' command contains a argument of type ' + args[i].constructor.name + '.\n' +
  915 + 'This is converted to "' + args[i].toString() + '" by using .toString() now and will return an error from v.3.0 on.\n' +
  916 + 'Please handle this in your code to make sure everything works as you intended it to.'
  917 + );
  918 + args_copy[i] = args[i].toString(); // Backwards compatible :/
  919 + }
  920 + } else if (typeof args[i] === 'undefined') {
  921 + this.warn(
  922 + 'Deprecated: The ' + command.toUpperCase() + ' command contains a "undefined" argument.\n' +
  923 + 'This is converted to a "undefined" string now and will return an error from v.3.0 on.\n' +
  924 + 'Please handle this in your code to make sure everything works as you intended it to.'
  925 + );
  926 + args_copy[i] = 'undefined'; // Backwards compatible :/
  927 + } else {
  928 + // Seems like numbers are converted fast using string concatenation
  929 + args_copy[i] = '' + args[i];
  930 + }
  931 + }
  932 +
  933 + if (this.options.prefix) {
  934 + prefix_keys = commands.getKeyIndexes(command, args_copy);
  935 + for (i = prefix_keys.pop(); i !== undefined; i = prefix_keys.pop()) {
  936 + args_copy[i] = this.options.prefix + args_copy[i];
  937 + }
  938 + }
  939 + if (this.options.rename_commands && this.options.rename_commands[command]) {
  940 + command = this.options.rename_commands[command];
  941 + }
  942 + // Always use 'Multi bulk commands', but if passed any Buffer args, then do multiple writes, one for each arg.
  943 + // This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer.
  944 + command_str = '*' + (len + 1) + '\r\n$' + command.length + '\r\n' + command + '\r\n';
  945 +
  946 + if (big_data === false) { // Build up a string and send entire command in one write
  947 + for (i = 0; i < len; i += 1) {
  948 + arg = args_copy[i];
  949 + command_str += '$' + Buffer.byteLength(arg) + '\r\n' + arg + '\r\n';
  950 + }
  951 + debug('Send ' + this.address + ' id ' + this.connection_id + ': ' + command_str);
  952 + this.write(command_str);
  953 + } else {
  954 + debug('Send command (' + command_str + ') has Buffer arguments');
  955 + this.fire_strings = false;
  956 + this.write(command_str);
  957 +
  958 + for (i = 0; i < len; i += 1) {
  959 + arg = args_copy[i];
  960 + if (typeof arg === 'string') {
  961 + this.write('$' + Buffer.byteLength(arg) + '\r\n' + arg + '\r\n');
  962 + } else { // buffer
  963 + this.write('$' + arg.length + '\r\n');
  964 + this.write(arg);
  965 + this.write('\r\n');
  966 + }
  967 + debug('send_command: buffer send ' + arg.length + ' bytes');
  968 + }
  969 + }
  970 + if (command_obj.call_on_write) {
  971 + command_obj.call_on_write();
  972 + }
  973 + // Handle `CLIENT REPLY ON|OFF|SKIP`
  974 + // This has to be checked after call_on_write
  975 + /* istanbul ignore else: TODO: Remove this as soon as we test Redis 3.2 on travis */
  976 + if (this.reply === 'ON') {
  977 + this.command_queue.push(command_obj);
  978 + } else {
  979 + // Do not expect a reply
  980 + // Does this work in combination with the pub sub mode?
  981 + if (command_obj.callback) {
  982 + utils.reply_in_order(this, command_obj.callback, null, undefined, this.command_queue);
  983 + }
  984 + if (this.reply === 'SKIP') {
  985 + this.reply = 'SKIP_ONE_MORE';
  986 + } else if (this.reply === 'SKIP_ONE_MORE') {
  987 + this.reply = 'ON';
  988 + }
  989 + }
  990 + return !this.should_buffer;
  991 +};
  992 +
  993 +RedisClient.prototype.write_strings = function () {
  994 + var str = '';
  995 + for (var command = this.pipeline_queue.shift(); command; command = this.pipeline_queue.shift()) {
  996 + // Write to stream if the string is bigger than 4mb. The biggest string may be Math.pow(2, 28) - 15 chars long
  997 + if (str.length + command.length > 4 * 1024 * 1024) {
  998 + this.should_buffer = !this.stream.write(str);
  999 + str = '';
  1000 + }
  1001 + str += command;
  1002 + }
  1003 + if (str !== '') {
  1004 + this.should_buffer = !this.stream.write(str);
  1005 + }
  1006 +};
  1007 +
  1008 +RedisClient.prototype.write_buffers = function () {
  1009 + for (var command = this.pipeline_queue.shift(); command; command = this.pipeline_queue.shift()) {
  1010 + this.should_buffer = !this.stream.write(command);
  1011 + }
  1012 +};
  1013 +
  1014 +RedisClient.prototype.write = function (data) {
  1015 + if (this.pipeline === false) {
  1016 + this.should_buffer = !this.stream.write(data);
  1017 + return;
  1018 + }
  1019 + this.pipeline_queue.push(data);
  1020 +};
  1021 +
  1022 +Object.defineProperty(exports, 'debugMode', {
  1023 + get: function () {
  1024 + return this.debug_mode;
  1025 + },
  1026 + set: function (val) {
  1027 + this.debug_mode = val;
  1028 + }
  1029 +});
  1030 +
  1031 +// Don't officially expose the command_queue directly but only the length as read only variable
  1032 +Object.defineProperty(RedisClient.prototype, 'command_queue_length', {
  1033 + get: function () {
  1034 + return this.command_queue.length;
  1035 + }
  1036 +});
  1037 +
  1038 +Object.defineProperty(RedisClient.prototype, 'offline_queue_length', {
  1039 + get: function () {
  1040 + return this.offline_queue.length;
  1041 + }
  1042 +});
  1043 +
  1044 +// Add support for camelCase by adding read only properties to the client
  1045 +// All known exposed snake_case variables are added here
  1046 +Object.defineProperty(RedisClient.prototype, 'retryDelay', {
  1047 + get: function () {
  1048 + return this.retry_delay;
  1049 + }
  1050 +});
  1051 +
  1052 +Object.defineProperty(RedisClient.prototype, 'retryBackoff', {
  1053 + get: function () {
  1054 + return this.retry_backoff;
  1055 + }
  1056 +});
  1057 +
  1058 +Object.defineProperty(RedisClient.prototype, 'commandQueueLength', {
  1059 + get: function () {
  1060 + return this.command_queue.length;
  1061 + }
  1062 +});
  1063 +
  1064 +Object.defineProperty(RedisClient.prototype, 'offlineQueueLength', {
  1065 + get: function () {
  1066 + return this.offline_queue.length;
  1067 + }
  1068 +});
  1069 +
  1070 +Object.defineProperty(RedisClient.prototype, 'shouldBuffer', {
  1071 + get: function () {
  1072 + return this.should_buffer;
  1073 + }
  1074 +});
  1075 +
  1076 +Object.defineProperty(RedisClient.prototype, 'connectionId', {
  1077 + get: function () {
  1078 + return this.connection_id;
  1079 + }
  1080 +});
  1081 +
  1082 +Object.defineProperty(RedisClient.prototype, 'serverInfo', {
  1083 + get: function () {
  1084 + return this.server_info;
  1085 + }
  1086 +});
  1087 +
  1088 +exports.createClient = function () {
  1089 + return new RedisClient(unifyOptions.apply(null, arguments));
  1090 +};
  1091 +exports.RedisClient = RedisClient;
  1092 +exports.print = utils.print;
  1093 +exports.Multi = require('./lib/multi');
  1094 +exports.AbortError = errorClasses.AbortError;
  1095 +exports.RedisError = Parser.RedisError;
  1096 +exports.ParserError = Parser.ParserError;
  1097 +exports.ReplyError = Parser.ReplyError;
  1098 +exports.AggregateError = errorClasses.AggregateError;
  1099 +
  1100 +// Add all redis commands / node_redis api to the client
  1101 +require('./lib/individualCommands');
  1102 +require('./lib/extendedApi');
  1103 +
  1104 +//enables adding new commands (for modules and new commands)
  1105 +exports.addCommand = exports.add_command = require('./lib/commands');
  1 +'use strict';
  2 +
  3 +var betterStackTraces = /development/i.test(process.env.NODE_ENV) || /\bredis\b/i.test(process.env.NODE_DEBUG);
  4 +
  5 +function Command (command, args, callback, call_on_write) {
  6 + this.command = command;
  7 + this.args = args;
  8 + this.buffer_args = false;
  9 + this.callback = callback;
  10 + this.call_on_write = call_on_write;
  11 + if (betterStackTraces) {
  12 + this.error = new Error();
  13 + }
  14 +}
  15 +
  16 +module.exports = Command;
  1 +'use strict';
  2 +
  3 +var commands = require('redis-commands');
  4 +var Multi = require('./multi');
  5 +var RedisClient = require('../').RedisClient;
  6 +var Command = require('./command');
  7 +// Feature detect if a function may change it's name
  8 +var changeFunctionName = (function () {
  9 + var fn = function abc () {};
  10 + try {
  11 + Object.defineProperty(fn, 'name', {
  12 + value: 'foobar'
  13 + });
  14 + return true;
  15 + } catch (e) {
  16 + return false;
  17 + }
  18 +}());
  19 +
  20 +var addCommand = function (command) {
  21 + // Some rare Redis commands use special characters in their command name
  22 + // Convert those to a underscore to prevent using invalid function names
  23 + var commandName = command.replace(/(?:^([0-9])|[^a-zA-Z0-9_$])/g, '_$1');
  24 +
  25 + // Do not override existing functions
  26 + if (!RedisClient.prototype[command]) {
  27 + RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function () {
  28 + var arr;
  29 + var len = arguments.length;
  30 + var callback;
  31 + var i = 0;
  32 + if (Array.isArray(arguments[0])) {
  33 + arr = arguments[0];
  34 + if (len === 2) {
  35 + callback = arguments[1];
  36 + }
  37 + } else if (len > 1 && Array.isArray(arguments[1])) {
  38 + if (len === 3) {
  39 + callback = arguments[2];
  40 + }
  41 + len = arguments[1].length;
  42 + arr = new Array(len + 1);
  43 + arr[0] = arguments[0];
  44 + for (; i < len; i += 1) {
  45 + arr[i + 1] = arguments[1][i];
  46 + }
  47 + } else {
  48 + // The later should not be the average use case
  49 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  50 + len--;
  51 + callback = arguments[len];
  52 + }
  53 + arr = new Array(len);
  54 + for (; i < len; i += 1) {
  55 + arr[i] = arguments[i];
  56 + }
  57 + }
  58 + return this.internal_send_command(new Command(command, arr, callback));
  59 + };
  60 + // Alias special function names (e.g. NR.RUN becomes NR_RUN and nr_run)
  61 + if (commandName !== command) {
  62 + RedisClient.prototype[commandName.toUpperCase()] = RedisClient.prototype[commandName] = RedisClient.prototype[command];
  63 + }
  64 + if (changeFunctionName) {
  65 + Object.defineProperty(RedisClient.prototype[command], 'name', {
  66 + value: commandName
  67 + });
  68 + }
  69 + }
  70 +
  71 + // Do not override existing functions
  72 + if (!Multi.prototype[command]) {
  73 + Multi.prototype[command.toUpperCase()] = Multi.prototype[command] = function () {
  74 + var arr;
  75 + var len = arguments.length;
  76 + var callback;
  77 + var i = 0;
  78 + if (Array.isArray(arguments[0])) {
  79 + arr = arguments[0];
  80 + if (len === 2) {
  81 + callback = arguments[1];
  82 + }
  83 + } else if (len > 1 && Array.isArray(arguments[1])) {
  84 + if (len === 3) {
  85 + callback = arguments[2];
  86 + }
  87 + len = arguments[1].length;
  88 + arr = new Array(len + 1);
  89 + arr[0] = arguments[0];
  90 + for (; i < len; i += 1) {
  91 + arr[i + 1] = arguments[1][i];
  92 + }
  93 + } else {
  94 + // The later should not be the average use case
  95 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  96 + len--;
  97 + callback = arguments[len];
  98 + }
  99 + arr = new Array(len);
  100 + for (; i < len; i += 1) {
  101 + arr[i] = arguments[i];
  102 + }
  103 + }
  104 + this.queue.push(new Command(command, arr, callback));
  105 + return this;
  106 + };
  107 + // Alias special function names (e.g. NR.RUN becomes NR_RUN and nr_run)
  108 + if (commandName !== command) {
  109 + Multi.prototype[commandName.toUpperCase()] = Multi.prototype[commandName] = Multi.prototype[command];
  110 + }
  111 + if (changeFunctionName) {
  112 + Object.defineProperty(Multi.prototype[command], 'name', {
  113 + value: commandName
  114 + });
  115 + }
  116 + }
  117 +};
  118 +
  119 +commands.list.forEach(addCommand);
  120 +
  121 +module.exports = addCommand;
  1 +'use strict';
  2 +
  3 +var utils = require('./utils');
  4 +var URL = require('url');
  5 +
  6 +module.exports = function createClient (port_arg, host_arg, options) {
  7 +
  8 + if (typeof port_arg === 'number' || typeof port_arg === 'string' && /^\d+$/.test(port_arg)) {
  9 +
  10 + var host;
  11 + if (typeof host_arg === 'string') {
  12 + host = host_arg;
  13 + } else {
  14 + if (options && host_arg) {
  15 + throw new TypeError('Unknown type of connection in createClient()');
  16 + }
  17 + options = options || host_arg;
  18 + }
  19 + options = utils.clone(options);
  20 + options.host = host || options.host;
  21 + options.port = port_arg;
  22 +
  23 + } else if (typeof port_arg === 'string' || port_arg && port_arg.url) {
  24 +
  25 + options = utils.clone(port_arg.url ? port_arg : host_arg || options);
  26 + var url = port_arg.url || port_arg;
  27 + var parsed = URL.parse(url, true, true);
  28 +
  29 + // [redis:]//[[user][:password]@][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]
  30 + if (parsed.slashes) { // We require slashes
  31 + if (parsed.auth) {
  32 + options.password = parsed.auth.split(':')[1];
  33 + }
  34 + if (parsed.protocol && parsed.protocol !== 'redis:') {
  35 + console.warn('node_redis: WARNING: You passed "' + parsed.protocol.substring(0, parsed.protocol.length - 1) + '" as protocol instead of the "redis" protocol!');
  36 + }
  37 + if (parsed.pathname && parsed.pathname !== '/') {
  38 + options.db = parsed.pathname.substr(1);
  39 + }
  40 + if (parsed.hostname) {
  41 + options.host = parsed.hostname;
  42 + }
  43 + if (parsed.port) {
  44 + options.port = parsed.port;
  45 + }
  46 + if (parsed.search !== '') {
  47 + var elem;
  48 + for (elem in parsed.query) {
  49 + // If options are passed twice, only the parsed options will be used
  50 + if (elem in options) {
  51 + if (options[elem] === parsed.query[elem]) {
  52 + console.warn('node_redis: WARNING: You passed the ' + elem + ' option twice!');
  53 + } else {
  54 + throw new RangeError('The ' + elem + ' option is added twice and does not match');
  55 + }
  56 + }
  57 + options[elem] = parsed.query[elem];
  58 + }
  59 + }
  60 + } else if (parsed.hostname) {
  61 + throw new RangeError('The redis url must begin with slashes "//" or contain slashes after the redis protocol');
  62 + } else {
  63 + options.path = url;
  64 + }
  65 +
  66 + } else if (typeof port_arg === 'object' || port_arg === undefined) {
  67 + options = utils.clone(port_arg || options);
  68 + options.host = options.host || host_arg;
  69 +
  70 + if (port_arg && arguments.length !== 1) {
  71 + throw new TypeError('To many arguments passed to createClient. Please only pass the options object');
  72 + }
  73 + }
  74 +
  75 + if (!options) {
  76 + throw new TypeError('Unknown type of connection in createClient()');
  77 + }
  78 +
  79 + return options;
  80 +};
  1 +'use strict';
  2 +
  3 +var util = require('util');
  4 +var assert = require('assert');
  5 +var RedisError = require('redis-parser').RedisError;
  6 +var ADD_STACKTRACE = false;
  7 +
  8 +function AbortError (obj, stack) {
  9 + assert(obj, 'The options argument is required');
  10 + assert.strictEqual(typeof obj, 'object', 'The options argument has to be of type object');
  11 +
  12 + RedisError.call(this, obj.message, ADD_STACKTRACE);
  13 + Object.defineProperty(this, 'message', {
  14 + value: obj.message || '',
  15 + configurable: true,
  16 + writable: true
  17 + });
  18 + if (stack || stack === undefined) {
  19 + Error.captureStackTrace(this, AbortError);
  20 + }
  21 + for (var keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) {
  22 + this[key] = obj[key];
  23 + }
  24 +}
  25 +
  26 +function AggregateError (obj) {
  27 + assert(obj, 'The options argument is required');
  28 + assert.strictEqual(typeof obj, 'object', 'The options argument has to be of type object');
  29 +
  30 + AbortError.call(this, obj, ADD_STACKTRACE);
  31 + Object.defineProperty(this, 'message', {
  32 + value: obj.message || '',
  33 + configurable: true,
  34 + writable: true
  35 + });
  36 + Error.captureStackTrace(this, AggregateError);
  37 + for (var keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) {
  38 + this[key] = obj[key];
  39 + }
  40 +}
  41 +
  42 +util.inherits(AbortError, RedisError);
  43 +util.inherits(AggregateError, AbortError);
  44 +
  45 +Object.defineProperty(AbortError.prototype, 'name', {
  46 + value: 'AbortError',
  47 + configurable: true,
  48 + writable: true
  49 +});
  50 +Object.defineProperty(AggregateError.prototype, 'name', {
  51 + value: 'AggregateError',
  52 + configurable: true,
  53 + writable: true
  54 +});
  55 +
  56 +module.exports = {
  57 + AbortError: AbortError,
  58 + AggregateError: AggregateError
  59 +};
  1 +'use strict';
  2 +
  3 +var index = require('../');
  4 +
  5 +function debug () {
  6 + if (index.debug_mode) {
  7 + console.error.apply(null, arguments);
  8 + }
  9 +}
  10 +
  11 +module.exports = debug;
  1 +'use strict';
  2 +
  3 +var utils = require('./utils');
  4 +var debug = require('./debug');
  5 +var RedisClient = require('../').RedisClient;
  6 +var Command = require('./command');
  7 +var noop = function () {};
  8 +
  9 +/**********************************************
  10 +All documented and exposed API belongs in here
  11 +**********************************************/
  12 +
  13 +// Redirect calls to the appropriate function and use to send arbitrary / not supported commands
  14 +RedisClient.prototype.send_command = RedisClient.prototype.sendCommand = function (command, args, callback) {
  15 + // Throw to fail early instead of relying in order in this case
  16 + if (typeof command !== 'string') {
  17 + throw new TypeError('Wrong input type "' + (command !== null && command !== undefined ? command.constructor.name : command) + '" for command name');
  18 + }
  19 + command = command.toLowerCase();
  20 + if (!Array.isArray(args)) {
  21 + if (args === undefined || args === null) {
  22 + args = [];
  23 + } else if (typeof args === 'function' && callback === undefined) {
  24 + callback = args;
  25 + args = [];
  26 + } else {
  27 + throw new TypeError('Wrong input type "' + args.constructor.name + '" for args');
  28 + }
  29 + }
  30 + if (typeof callback !== 'function' && callback !== undefined) {
  31 + throw new TypeError('Wrong input type "' + (callback !== null ? callback.constructor.name : 'null') + '" for callback function');
  32 + }
  33 +
  34 + // Using the raw multi command is only possible with this function
  35 + // If the command is not yet added to the client, the internal function should be called right away
  36 + // Otherwise we need to redirect the calls to make sure the internal functions don't get skipped
  37 + // The internal functions could actually be used for any non hooked function
  38 + // but this might change from time to time and at the moment there's no good way to distinguish them
  39 + // from each other, so let's just do it do it this way for the time being
  40 + if (command === 'multi' || typeof this[command] !== 'function') {
  41 + return this.internal_send_command(new Command(command, args, callback));
  42 + }
  43 + if (typeof callback === 'function') {
  44 + args = args.concat([callback]); // Prevent manipulating the input array
  45 + }
  46 + return this[command].apply(this, args);
  47 +};
  48 +
  49 +RedisClient.prototype.end = function (flush) {
  50 + // Flush queue if wanted
  51 + if (flush) {
  52 + this.flush_and_error({
  53 + message: 'Connection forcefully ended and command aborted.',
  54 + code: 'NR_CLOSED'
  55 + });
  56 + } else if (arguments.length === 0) {
  57 + this.warn(
  58 + 'Using .end() without the flush parameter is deprecated and throws from v.3.0.0 on.\n' +
  59 + 'Please check the doku (https://github.com/NodeRedis/node_redis) and explictly use flush.'
  60 + );
  61 + }
  62 + // Clear retry_timer
  63 + if (this.retry_timer) {
  64 + clearTimeout(this.retry_timer);
  65 + this.retry_timer = null;
  66 + }
  67 + this.stream.removeAllListeners();
  68 + this.stream.on('error', noop);
  69 + this.connected = false;
  70 + this.ready = false;
  71 + this.closing = true;
  72 + return this.stream.destroySoon();
  73 +};
  74 +
  75 +RedisClient.prototype.unref = function () {
  76 + if (this.connected) {
  77 + debug("Unref'ing the socket connection");
  78 + this.stream.unref();
  79 + } else {
  80 + debug('Not connected yet, will unref later');
  81 + this.once('connect', function () {
  82 + this.unref();
  83 + });
  84 + }
  85 +};
  86 +
  87 +RedisClient.prototype.duplicate = function (options, callback) {
  88 + if (typeof options === 'function') {
  89 + callback = options;
  90 + options = null;
  91 + }
  92 + var existing_options = utils.clone(this.options);
  93 + options = utils.clone(options);
  94 + for (var elem in options) {
  95 + existing_options[elem] = options[elem];
  96 + }
  97 + var client = new RedisClient(existing_options);
  98 + client.selected_db = this.selected_db;
  99 + if (typeof callback === 'function') {
  100 + var ready_listener = function () {
  101 + callback(null, client);
  102 + client.removeAllListeners(error_listener);
  103 + };
  104 + var error_listener = function (err) {
  105 + callback(err);
  106 + client.end(true);
  107 + };
  108 + client.once('ready', ready_listener);
  109 + client.once('error', error_listener);
  110 + return;
  111 + }
  112 + return client;
  113 +};
  1 +'use strict';
  2 +
  3 +var utils = require('./utils');
  4 +var debug = require('./debug');
  5 +var Multi = require('./multi');
  6 +var Command = require('./command');
  7 +var no_password_is_set = /no password is set/;
  8 +var loading = /LOADING/;
  9 +var RedisClient = require('../').RedisClient;
  10 +
  11 +/********************************************************************************************
  12 + Replace built-in redis functions
  13 +
  14 + The callback may be hooked as needed. The same does not apply to the rest of the function.
  15 + State should not be set outside of the callback if not absolutly necessary.
  16 + This is important to make sure it works the same as single command or in a multi context.
  17 + To make sure everything works with the offline queue use the "call_on_write" function.
  18 + This is going to be executed while writing to the stream.
  19 +
  20 + TODO: Implement individal command generation as soon as possible to prevent divergent code
  21 + on single and multi calls!
  22 +********************************************************************************************/
  23 +
  24 +RedisClient.prototype.multi = RedisClient.prototype.MULTI = function multi (args) {
  25 + var multi = new Multi(this, args);
  26 + multi.exec = multi.EXEC = multi.exec_transaction;
  27 + return multi;
  28 +};
  29 +
  30 +// ATTENTION: This is not a native function but is still handled as a individual command as it behaves just the same as multi
  31 +RedisClient.prototype.batch = RedisClient.prototype.BATCH = function batch (args) {
  32 + return new Multi(this, args);
  33 +};
  34 +
  35 +function select_callback (self, db, callback) {
  36 + return function (err, res) {
  37 + if (err === null) {
  38 + // Store db in this.select_db to restore it on reconnect
  39 + self.selected_db = db;
  40 + }
  41 + utils.callback_or_emit(self, callback, err, res);
  42 + };
  43 +}
  44 +
  45 +RedisClient.prototype.select = RedisClient.prototype.SELECT = function select (db, callback) {
  46 + return this.internal_send_command(new Command('select', [db], select_callback(this, db, callback)));
  47 +};
  48 +
  49 +Multi.prototype.select = Multi.prototype.SELECT = function select (db, callback) {
  50 + this.queue.push(new Command('select', [db], select_callback(this._client, db, callback)));
  51 + return this;
  52 +};
  53 +
  54 +RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function monitor (callback) {
  55 + // Use a individual command, as this is a special case that does not has to be checked for any other command
  56 + var self = this;
  57 + var call_on_write = function () {
  58 + // Activating monitor mode has to happen before Redis returned the callback. The monitor result is returned first.
  59 + // Therefore we expect the command to be properly processed. If this is not the case, it's not an issue either.
  60 + self.monitoring = true;
  61 + };
  62 + return this.internal_send_command(new Command('monitor', [], callback, call_on_write));
  63 +};
  64 +
  65 +// Only works with batch, not in a transaction
  66 +Multi.prototype.monitor = Multi.prototype.MONITOR = function monitor (callback) {
  67 + // Use a individual command, as this is a special case that does not has to be checked for any other command
  68 + if (this.exec !== this.exec_transaction) {
  69 + var self = this;
  70 + var call_on_write = function () {
  71 + self._client.monitoring = true;
  72 + };
  73 + this.queue.push(new Command('monitor', [], callback, call_on_write));
  74 + return this;
  75 + }
  76 + // Set multi monitoring to indicate the exec that it should abort
  77 + // Remove this "hack" as soon as Redis might fix this
  78 + this.monitoring = true;
  79 + return this;
  80 +};
  81 +
  82 +function quit_callback (self, callback) {
  83 + return function (err, res) {
  84 + if (err && err.code === 'NR_CLOSED') {
  85 + // Pretent the quit command worked properly in this case.
  86 + // Either the quit landed in the offline queue and was flushed at the reconnect
  87 + // or the offline queue is deactivated and the command was rejected right away
  88 + // or the stream is not writable
  89 + // or while sending the quit, the connection ended / closed
  90 + err = null;
  91 + res = 'OK';
  92 + }
  93 + utils.callback_or_emit(self, callback, err, res);
  94 + if (self.stream.writable) {
  95 + // If the socket is still alive, kill it. This could happen if quit got a NR_CLOSED error code
  96 + self.stream.destroy();
  97 + }
  98 + };
  99 +}
  100 +
  101 +RedisClient.prototype.QUIT = RedisClient.prototype.quit = function quit (callback) {
  102 + // TODO: Consider this for v.3
  103 + // Allow the quit command to be fired as soon as possible to prevent it landing in the offline queue.
  104 + // this.ready = this.offline_queue.length === 0;
  105 + var backpressure_indicator = this.internal_send_command(new Command('quit', [], quit_callback(this, callback)));
  106 + // Calling quit should always end the connection, no matter if there's a connection or not
  107 + this.closing = true;
  108 + this.ready = false;
  109 + return backpressure_indicator;
  110 +};
  111 +
  112 +// Only works with batch, not in a transaction
  113 +Multi.prototype.QUIT = Multi.prototype.quit = function quit (callback) {
  114 + var self = this._client;
  115 + var call_on_write = function () {
  116 + // If called in a multi context, we expect redis is available
  117 + self.closing = true;
  118 + self.ready = false;
  119 + };
  120 + this.queue.push(new Command('quit', [], quit_callback(self, callback), call_on_write));
  121 + return this;
  122 +};
  123 +
  124 +function info_callback (self, callback) {
  125 + return function (err, res) {
  126 + if (res) {
  127 + var obj = {};
  128 + var lines = res.toString().split('\r\n');
  129 + var line, parts, sub_parts;
  130 +
  131 + for (var i = 0; i < lines.length; i++) {
  132 + parts = lines[i].split(':');
  133 + if (parts[1]) {
  134 + if (parts[0].indexOf('db') === 0) {
  135 + sub_parts = parts[1].split(',');
  136 + obj[parts[0]] = {};
  137 + while (line = sub_parts.pop()) {
  138 + line = line.split('=');
  139 + obj[parts[0]][line[0]] = +line[1];
  140 + }
  141 + } else {
  142 + obj[parts[0]] = parts[1];
  143 + }
  144 + }
  145 + }
  146 + obj.versions = [];
  147 + if (obj.redis_version) {
  148 + obj.redis_version.split('.').forEach(function (num) {
  149 + obj.versions.push(+num);
  150 + });
  151 + }
  152 + // Expose info key/vals to users
  153 + self.server_info = obj;
  154 + } else {
  155 + self.server_info = {};
  156 + }
  157 + utils.callback_or_emit(self, callback, err, res);
  158 + };
  159 +}
  160 +
  161 +// Store info in this.server_info after each call
  162 +RedisClient.prototype.info = RedisClient.prototype.INFO = function info (section, callback) {
  163 + var args = [];
  164 + if (typeof section === 'function') {
  165 + callback = section;
  166 + } else if (section !== undefined) {
  167 + args = Array.isArray(section) ? section : [section];
  168 + }
  169 + return this.internal_send_command(new Command('info', args, info_callback(this, callback)));
  170 +};
  171 +
  172 +Multi.prototype.info = Multi.prototype.INFO = function info (section, callback) {
  173 + var args = [];
  174 + if (typeof section === 'function') {
  175 + callback = section;
  176 + } else if (section !== undefined) {
  177 + args = Array.isArray(section) ? section : [section];
  178 + }
  179 + this.queue.push(new Command('info', args, info_callback(this._client, callback)));
  180 + return this;
  181 +};
  182 +
  183 +function auth_callback (self, pass, callback) {
  184 + return function (err, res) {
  185 + if (err) {
  186 + if (no_password_is_set.test(err.message)) {
  187 + self.warn('Warning: Redis server does not require a password, but a password was supplied.');
  188 + err = null;
  189 + res = 'OK';
  190 + } else if (loading.test(err.message)) {
  191 + // If redis is still loading the db, it will not authenticate and everything else will fail
  192 + debug('Redis still loading, trying to authenticate later');
  193 + setTimeout(function () {
  194 + self.auth(pass, callback);
  195 + }, 100);
  196 + return;
  197 + }
  198 + }
  199 + utils.callback_or_emit(self, callback, err, res);
  200 + };
  201 +}
  202 +
  203 +RedisClient.prototype.auth = RedisClient.prototype.AUTH = function auth (pass, callback) {
  204 + debug('Sending auth to ' + this.address + ' id ' + this.connection_id);
  205 +
  206 + // Stash auth for connect and reconnect.
  207 + this.auth_pass = pass;
  208 + var ready = this.ready;
  209 + this.ready = ready || this.offline_queue.length === 0;
  210 + var tmp = this.internal_send_command(new Command('auth', [pass], auth_callback(this, pass, callback)));
  211 + this.ready = ready;
  212 + return tmp;
  213 +};
  214 +
  215 +// Only works with batch, not in a transaction
  216 +Multi.prototype.auth = Multi.prototype.AUTH = function auth (pass, callback) {
  217 + debug('Sending auth to ' + this.address + ' id ' + this.connection_id);
  218 +
  219 + // Stash auth for connect and reconnect.
  220 + this.auth_pass = pass;
  221 + this.queue.push(new Command('auth', [pass], auth_callback(this._client, callback)));
  222 + return this;
  223 +};
  224 +
  225 +RedisClient.prototype.client = RedisClient.prototype.CLIENT = function client () {
  226 + var arr,
  227 + len = arguments.length,
  228 + callback,
  229 + i = 0;
  230 + if (Array.isArray(arguments[0])) {
  231 + arr = arguments[0];
  232 + callback = arguments[1];
  233 + } else if (Array.isArray(arguments[1])) {
  234 + if (len === 3) {
  235 + callback = arguments[2];
  236 + }
  237 + len = arguments[1].length;
  238 + arr = new Array(len + 1);
  239 + arr[0] = arguments[0];
  240 + for (; i < len; i += 1) {
  241 + arr[i + 1] = arguments[1][i];
  242 + }
  243 + } else {
  244 + len = arguments.length;
  245 + // The later should not be the average use case
  246 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  247 + len--;
  248 + callback = arguments[len];
  249 + }
  250 + arr = new Array(len);
  251 + for (; i < len; i += 1) {
  252 + arr[i] = arguments[i];
  253 + }
  254 + }
  255 + var self = this;
  256 + var call_on_write = undefined;
  257 + // CLIENT REPLY ON|OFF|SKIP
  258 + /* istanbul ignore next: TODO: Remove this as soon as Travis runs Redis 3.2 */
  259 + if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') {
  260 + var reply_on_off = arr[1].toString().toUpperCase();
  261 + if (reply_on_off === 'ON' || reply_on_off === 'OFF' || reply_on_off === 'SKIP') {
  262 + call_on_write = function () {
  263 + self.reply = reply_on_off;
  264 + };
  265 + }
  266 + }
  267 + return this.internal_send_command(new Command('client', arr, callback, call_on_write));
  268 +};
  269 +
  270 +Multi.prototype.client = Multi.prototype.CLIENT = function client () {
  271 + var arr,
  272 + len = arguments.length,
  273 + callback,
  274 + i = 0;
  275 + if (Array.isArray(arguments[0])) {
  276 + arr = arguments[0];
  277 + callback = arguments[1];
  278 + } else if (Array.isArray(arguments[1])) {
  279 + if (len === 3) {
  280 + callback = arguments[2];
  281 + }
  282 + len = arguments[1].length;
  283 + arr = new Array(len + 1);
  284 + arr[0] = arguments[0];
  285 + for (; i < len; i += 1) {
  286 + arr[i + 1] = arguments[1][i];
  287 + }
  288 + } else {
  289 + len = arguments.length;
  290 + // The later should not be the average use case
  291 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  292 + len--;
  293 + callback = arguments[len];
  294 + }
  295 + arr = new Array(len);
  296 + for (; i < len; i += 1) {
  297 + arr[i] = arguments[i];
  298 + }
  299 + }
  300 + var self = this._client;
  301 + var call_on_write = undefined;
  302 + // CLIENT REPLY ON|OFF|SKIP
  303 + /* istanbul ignore next: TODO: Remove this as soon as Travis runs Redis 3.2 */
  304 + if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') {
  305 + var reply_on_off = arr[1].toString().toUpperCase();
  306 + if (reply_on_off === 'ON' || reply_on_off === 'OFF' || reply_on_off === 'SKIP') {
  307 + call_on_write = function () {
  308 + self.reply = reply_on_off;
  309 + };
  310 + }
  311 + }
  312 + this.queue.push(new Command('client', arr, callback, call_on_write));
  313 + return this;
  314 +};
  315 +
  316 +RedisClient.prototype.hmset = RedisClient.prototype.HMSET = function hmset () {
  317 + var arr,
  318 + len = arguments.length,
  319 + callback,
  320 + i = 0;
  321 + if (Array.isArray(arguments[0])) {
  322 + arr = arguments[0];
  323 + callback = arguments[1];
  324 + } else if (Array.isArray(arguments[1])) {
  325 + if (len === 3) {
  326 + callback = arguments[2];
  327 + }
  328 + len = arguments[1].length;
  329 + arr = new Array(len + 1);
  330 + arr[0] = arguments[0];
  331 + for (; i < len; i += 1) {
  332 + arr[i + 1] = arguments[1][i];
  333 + }
  334 + } else if (typeof arguments[1] === 'object' && (arguments.length === 2 || arguments.length === 3 && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined'))) {
  335 + arr = [arguments[0]];
  336 + for (var field in arguments[1]) {
  337 + arr.push(field, arguments[1][field]);
  338 + }
  339 + callback = arguments[2];
  340 + } else {
  341 + len = arguments.length;
  342 + // The later should not be the average use case
  343 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  344 + len--;
  345 + callback = arguments[len];
  346 + }
  347 + arr = new Array(len);
  348 + for (; i < len; i += 1) {
  349 + arr[i] = arguments[i];
  350 + }
  351 + }
  352 + return this.internal_send_command(new Command('hmset', arr, callback));
  353 +};
  354 +
  355 +Multi.prototype.hmset = Multi.prototype.HMSET = function hmset () {
  356 + var arr,
  357 + len = arguments.length,
  358 + callback,
  359 + i = 0;
  360 + if (Array.isArray(arguments[0])) {
  361 + arr = arguments[0];
  362 + callback = arguments[1];
  363 + } else if (Array.isArray(arguments[1])) {
  364 + if (len === 3) {
  365 + callback = arguments[2];
  366 + }
  367 + len = arguments[1].length;
  368 + arr = new Array(len + 1);
  369 + arr[0] = arguments[0];
  370 + for (; i < len; i += 1) {
  371 + arr[i + 1] = arguments[1][i];
  372 + }
  373 + } else if (typeof arguments[1] === 'object' && (arguments.length === 2 || arguments.length === 3 && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined'))) {
  374 + arr = [arguments[0]];
  375 + for (var field in arguments[1]) {
  376 + arr.push(field, arguments[1][field]);
  377 + }
  378 + callback = arguments[2];
  379 + } else {
  380 + len = arguments.length;
  381 + // The later should not be the average use case
  382 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  383 + len--;
  384 + callback = arguments[len];
  385 + }
  386 + arr = new Array(len);
  387 + for (; i < len; i += 1) {
  388 + arr[i] = arguments[i];
  389 + }
  390 + }
  391 + this.queue.push(new Command('hmset', arr, callback));
  392 + return this;
  393 +};
  394 +
  395 +RedisClient.prototype.subscribe = RedisClient.prototype.SUBSCRIBE = function subscribe () {
  396 + var arr,
  397 + len = arguments.length,
  398 + callback,
  399 + i = 0;
  400 + if (Array.isArray(arguments[0])) {
  401 + arr = arguments[0].slice(0);
  402 + callback = arguments[1];
  403 + } else {
  404 + len = arguments.length;
  405 + // The later should not be the average use case
  406 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  407 + len--;
  408 + callback = arguments[len];
  409 + }
  410 + arr = new Array(len);
  411 + for (; i < len; i += 1) {
  412 + arr[i] = arguments[i];
  413 + }
  414 + }
  415 + var self = this;
  416 + var call_on_write = function () {
  417 + self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
  418 + };
  419 + return this.internal_send_command(new Command('subscribe', arr, callback, call_on_write));
  420 +};
  421 +
  422 +Multi.prototype.subscribe = Multi.prototype.SUBSCRIBE = function subscribe () {
  423 + var arr,
  424 + len = arguments.length,
  425 + callback,
  426 + i = 0;
  427 + if (Array.isArray(arguments[0])) {
  428 + arr = arguments[0].slice(0);
  429 + callback = arguments[1];
  430 + } else {
  431 + len = arguments.length;
  432 + // The later should not be the average use case
  433 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  434 + len--;
  435 + callback = arguments[len];
  436 + }
  437 + arr = new Array(len);
  438 + for (; i < len; i += 1) {
  439 + arr[i] = arguments[i];
  440 + }
  441 + }
  442 + var self = this._client;
  443 + var call_on_write = function () {
  444 + self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
  445 + };
  446 + this.queue.push(new Command('subscribe', arr, callback, call_on_write));
  447 + return this;
  448 +};
  449 +
  450 +RedisClient.prototype.unsubscribe = RedisClient.prototype.UNSUBSCRIBE = function unsubscribe () {
  451 + var arr,
  452 + len = arguments.length,
  453 + callback,
  454 + i = 0;
  455 + if (Array.isArray(arguments[0])) {
  456 + arr = arguments[0].slice(0);
  457 + callback = arguments[1];
  458 + } else {
  459 + len = arguments.length;
  460 + // The later should not be the average use case
  461 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  462 + len--;
  463 + callback = arguments[len];
  464 + }
  465 + arr = new Array(len);
  466 + for (; i < len; i += 1) {
  467 + arr[i] = arguments[i];
  468 + }
  469 + }
  470 + var self = this;
  471 + var call_on_write = function () {
  472 + // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
  473 + self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
  474 + };
  475 + return this.internal_send_command(new Command('unsubscribe', arr, callback, call_on_write));
  476 +};
  477 +
  478 +Multi.prototype.unsubscribe = Multi.prototype.UNSUBSCRIBE = function unsubscribe () {
  479 + var arr,
  480 + len = arguments.length,
  481 + callback,
  482 + i = 0;
  483 + if (Array.isArray(arguments[0])) {
  484 + arr = arguments[0].slice(0);
  485 + callback = arguments[1];
  486 + } else {
  487 + len = arguments.length;
  488 + // The later should not be the average use case
  489 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  490 + len--;
  491 + callback = arguments[len];
  492 + }
  493 + arr = new Array(len);
  494 + for (; i < len; i += 1) {
  495 + arr[i] = arguments[i];
  496 + }
  497 + }
  498 + var self = this._client;
  499 + var call_on_write = function () {
  500 + // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
  501 + self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
  502 + };
  503 + this.queue.push(new Command('unsubscribe', arr, callback, call_on_write));
  504 + return this;
  505 +};
  506 +
  507 +RedisClient.prototype.psubscribe = RedisClient.prototype.PSUBSCRIBE = function psubscribe () {
  508 + var arr,
  509 + len = arguments.length,
  510 + callback,
  511 + i = 0;
  512 + if (Array.isArray(arguments[0])) {
  513 + arr = arguments[0].slice(0);
  514 + callback = arguments[1];
  515 + } else {
  516 + len = arguments.length;
  517 + // The later should not be the average use case
  518 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  519 + len--;
  520 + callback = arguments[len];
  521 + }
  522 + arr = new Array(len);
  523 + for (; i < len; i += 1) {
  524 + arr[i] = arguments[i];
  525 + }
  526 + }
  527 + var self = this;
  528 + var call_on_write = function () {
  529 + self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
  530 + };
  531 + return this.internal_send_command(new Command('psubscribe', arr, callback, call_on_write));
  532 +};
  533 +
  534 +Multi.prototype.psubscribe = Multi.prototype.PSUBSCRIBE = function psubscribe () {
  535 + var arr,
  536 + len = arguments.length,
  537 + callback,
  538 + i = 0;
  539 + if (Array.isArray(arguments[0])) {
  540 + arr = arguments[0].slice(0);
  541 + callback = arguments[1];
  542 + } else {
  543 + len = arguments.length;
  544 + // The later should not be the average use case
  545 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  546 + len--;
  547 + callback = arguments[len];
  548 + }
  549 + arr = new Array(len);
  550 + for (; i < len; i += 1) {
  551 + arr[i] = arguments[i];
  552 + }
  553 + }
  554 + var self = this._client;
  555 + var call_on_write = function () {
  556 + self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
  557 + };
  558 + this.queue.push(new Command('psubscribe', arr, callback, call_on_write));
  559 + return this;
  560 +};
  561 +
  562 +RedisClient.prototype.punsubscribe = RedisClient.prototype.PUNSUBSCRIBE = function punsubscribe () {
  563 + var arr,
  564 + len = arguments.length,
  565 + callback,
  566 + i = 0;
  567 + if (Array.isArray(arguments[0])) {
  568 + arr = arguments[0].slice(0);
  569 + callback = arguments[1];
  570 + } else {
  571 + len = arguments.length;
  572 + // The later should not be the average use case
  573 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  574 + len--;
  575 + callback = arguments[len];
  576 + }
  577 + arr = new Array(len);
  578 + for (; i < len; i += 1) {
  579 + arr[i] = arguments[i];
  580 + }
  581 + }
  582 + var self = this;
  583 + var call_on_write = function () {
  584 + // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
  585 + self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
  586 + };
  587 + return this.internal_send_command(new Command('punsubscribe', arr, callback, call_on_write));
  588 +};
  589 +
  590 +Multi.prototype.punsubscribe = Multi.prototype.PUNSUBSCRIBE = function punsubscribe () {
  591 + var arr,
  592 + len = arguments.length,
  593 + callback,
  594 + i = 0;
  595 + if (Array.isArray(arguments[0])) {
  596 + arr = arguments[0].slice(0);
  597 + callback = arguments[1];
  598 + } else {
  599 + len = arguments.length;
  600 + // The later should not be the average use case
  601 + if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
  602 + len--;
  603 + callback = arguments[len];
  604 + }
  605 + arr = new Array(len);
  606 + for (; i < len; i += 1) {
  607 + arr[i] = arguments[i];
  608 + }
  609 + }
  610 + var self = this._client;
  611 + var call_on_write = function () {
  612 + // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
  613 + self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
  614 + };
  615 + this.queue.push(new Command('punsubscribe', arr, callback, call_on_write));
  616 + return this;
  617 +};
  1 +'use strict';
  2 +
  3 +var Queue = require('double-ended-queue');
  4 +var utils = require('./utils');
  5 +var Command = require('./command');
  6 +
  7 +function Multi (client, args) {
  8 + this._client = client;
  9 + this.queue = new Queue();
  10 + var command, tmp_args;
  11 + if (args) { // Either undefined or an array. Fail hard if it's not an array
  12 + for (var i = 0; i < args.length; i++) {
  13 + command = args[i][0];
  14 + tmp_args = args[i].slice(1);
  15 + if (Array.isArray(command)) {
  16 + this[command[0]].apply(this, command.slice(1).concat(tmp_args));
  17 + } else {
  18 + this[command].apply(this, tmp_args);
  19 + }
  20 + }
  21 + }
  22 +}
  23 +
  24 +function pipeline_transaction_command (self, command_obj, index) {
  25 + // Queueing is done first, then the commands are executed
  26 + var tmp = command_obj.callback;
  27 + command_obj.callback = function (err, reply) {
  28 + // Ignore the multi command. This is applied by node_redis and the user does not benefit by it
  29 + if (err && index !== -1) {
  30 + if (tmp) {
  31 + tmp(err);
  32 + }
  33 + err.position = index;
  34 + self.errors.push(err);
  35 + }
  36 + // Keep track of who wants buffer responses:
  37 + // By the time the callback is called the command_obj got the buffer_args attribute attached
  38 + self.wants_buffers[index] = command_obj.buffer_args;
  39 + command_obj.callback = tmp;
  40 + };
  41 + self._client.internal_send_command(command_obj);
  42 +}
  43 +
  44 +Multi.prototype.exec_atomic = Multi.prototype.EXEC_ATOMIC = Multi.prototype.execAtomic = function exec_atomic (callback) {
  45 + if (this.queue.length < 2) {
  46 + return this.exec_batch(callback);
  47 + }
  48 + return this.exec(callback);
  49 +};
  50 +
  51 +function multi_callback (self, err, replies) {
  52 + var i = 0, command_obj;
  53 +
  54 + if (err) {
  55 + err.errors = self.errors;
  56 + if (self.callback) {
  57 + self.callback(err);
  58 + // Exclude connection errors so that those errors won't be emitted twice
  59 + } else if (err.code !== 'CONNECTION_BROKEN') {
  60 + self._client.emit('error', err);
  61 + }
  62 + return;
  63 + }
  64 +
  65 + if (replies) {
  66 + while (command_obj = self.queue.shift()) {
  67 + if (replies[i] instanceof Error) {
  68 + var match = replies[i].message.match(utils.err_code);
  69 + // LUA script could return user errors that don't behave like all other errors!
  70 + if (match) {
  71 + replies[i].code = match[1];
  72 + }
  73 + replies[i].command = command_obj.command.toUpperCase();
  74 + if (typeof command_obj.callback === 'function') {
  75 + command_obj.callback(replies[i]);
  76 + }
  77 + } else {
  78 + // If we asked for strings, even in detect_buffers mode, then return strings:
  79 + replies[i] = self._client.handle_reply(replies[i], command_obj.command, self.wants_buffers[i]);
  80 + if (typeof command_obj.callback === 'function') {
  81 + command_obj.callback(null, replies[i]);
  82 + }
  83 + }
  84 + i++;
  85 + }
  86 + }
  87 +
  88 + if (self.callback) {
  89 + self.callback(null, replies);
  90 + }
  91 +}
  92 +
  93 +Multi.prototype.exec_transaction = function exec_transaction (callback) {
  94 + if (this.monitoring || this._client.monitoring) {
  95 + var err = new RangeError(
  96 + 'Using transaction with a client that is in monitor mode does not work due to faulty return values of Redis.'
  97 + );
  98 + err.command = 'EXEC';
  99 + err.code = 'EXECABORT';
  100 + return utils.reply_in_order(this._client, callback, err);
  101 + }
  102 + var self = this;
  103 + var len = self.queue.length;
  104 + self.errors = [];
  105 + self.callback = callback;
  106 + self._client.cork();
  107 + self.wants_buffers = new Array(len);
  108 + pipeline_transaction_command(self, new Command('multi', []), -1);
  109 + // Drain queue, callback will catch 'QUEUED' or error
  110 + for (var index = 0; index < len; index++) {
  111 + // The commands may not be shifted off, since they are needed in the result handler
  112 + pipeline_transaction_command(self, self.queue.get(index), index);
  113 + }
  114 +
  115 + self._client.internal_send_command(new Command('exec', [], function (err, replies) {
  116 + multi_callback(self, err, replies);
  117 + }));
  118 + self._client.uncork();
  119 + return !self._client.should_buffer;
  120 +};
  121 +
  122 +function batch_callback (self, cb, i) {
  123 + return function batch_callback (err, res) {
  124 + if (err) {
  125 + self.results[i] = err;
  126 + // Add the position to the error
  127 + self.results[i].position = i;
  128 + } else {
  129 + self.results[i] = res;
  130 + }
  131 + cb(err, res);
  132 + };
  133 +}
  134 +
  135 +Multi.prototype.exec = Multi.prototype.EXEC = Multi.prototype.exec_batch = function exec_batch (callback) {
  136 + var self = this;
  137 + var len = self.queue.length;
  138 + var index = 0;
  139 + var command_obj;
  140 + if (len === 0) {
  141 + utils.reply_in_order(self._client, callback, null, []);
  142 + return !self._client.should_buffer;
  143 + }
  144 + self._client.cork();
  145 + if (!callback) {
  146 + while (command_obj = self.queue.shift()) {
  147 + self._client.internal_send_command(command_obj);
  148 + }
  149 + self._client.uncork();
  150 + return !self._client.should_buffer;
  151 + }
  152 + var callback_without_own_cb = function (err, res) {
  153 + if (err) {
  154 + self.results.push(err);
  155 + // Add the position to the error
  156 + var i = self.results.length - 1;
  157 + self.results[i].position = i;
  158 + } else {
  159 + self.results.push(res);
  160 + }
  161 + // Do not emit an error here. Otherwise each error would result in one emit.
  162 + // The errors will be returned in the result anyway
  163 + };
  164 + var last_callback = function (cb) {
  165 + return function (err, res) {
  166 + cb(err, res);
  167 + callback(null, self.results);
  168 + };
  169 + };
  170 + self.results = [];
  171 + while (command_obj = self.queue.shift()) {
  172 + if (typeof command_obj.callback === 'function') {
  173 + command_obj.callback = batch_callback(self, command_obj.callback, index);
  174 + } else {
  175 + command_obj.callback = callback_without_own_cb;
  176 + }
  177 + if (typeof callback === 'function' && index === len - 1) {
  178 + command_obj.callback = last_callback(command_obj.callback);
  179 + }
  180 + this._client.internal_send_command(command_obj);
  181 + index++;
  182 + }
  183 + self._client.uncork();
  184 + return !self._client.should_buffer;
  185 +};
  186 +
  187 +module.exports = Multi;
  1 +'use strict';
  2 +
  3 +// hgetall converts its replies to an Object. If the reply is empty, null is returned.
  4 +// These function are only called with internal data and have therefore always the same instanceof X
  5 +function replyToObject (reply) {
  6 + // The reply might be a string or a buffer if this is called in a transaction (multi)
  7 + if (reply.length === 0 || !(reply instanceof Array)) {
  8 + return null;
  9 + }
  10 + var obj = {};
  11 + for (var i = 0; i < reply.length; i += 2) {
  12 + obj[reply[i].toString('binary')] = reply[i + 1];
  13 + }
  14 + return obj;
  15 +}
  16 +
  17 +function replyToStrings (reply) {
  18 + if (reply instanceof Buffer) {
  19 + return reply.toString();
  20 + }
  21 + if (reply instanceof Array) {
  22 + var res = new Array(reply.length);
  23 + for (var i = 0; i < reply.length; i++) {
  24 + // Recusivly call the function as slowlog returns deep nested replies
  25 + res[i] = replyToStrings(reply[i]);
  26 + }
  27 + return res;
  28 + }
  29 +
  30 + return reply;
  31 +}
  32 +
  33 +function print (err, reply) {
  34 + if (err) {
  35 + // A error always begins with Error:
  36 + console.log(err.toString());
  37 + } else {
  38 + console.log('Reply: ' + reply);
  39 + }
  40 +}
  41 +
  42 +var camelCase;
  43 +// Deep clone arbitrary objects with arrays. Can't handle cyclic structures (results in a range error)
  44 +// Any attribute with a non primitive value besides object and array will be passed by reference (e.g. Buffers, Maps, Functions)
  45 +// All capital letters are going to be replaced with a lower case letter and a underscore infront of it
  46 +function clone (obj) {
  47 + var copy;
  48 + if (Array.isArray(obj)) {
  49 + copy = new Array(obj.length);
  50 + for (var i = 0; i < obj.length; i++) {
  51 + copy[i] = clone(obj[i]);
  52 + }
  53 + return copy;
  54 + }
  55 + if (Object.prototype.toString.call(obj) === '[object Object]') {
  56 + copy = {};
  57 + var elems = Object.keys(obj);
  58 + var elem;
  59 + while (elem = elems.pop()) {
  60 + if (elem === 'tls') { // special handle tls
  61 + copy[elem] = obj[elem];
  62 + continue;
  63 + }
  64 + // Accept camelCase options and convert them to snake_case
  65 + var snake_case = elem.replace(/[A-Z][^A-Z]/g, '_$&').toLowerCase();
  66 + // If camelCase is detected, pass it to the client, so all variables are going to be camelCased
  67 + // There are no deep nested options objects yet, but let's handle this future proof
  68 + if (snake_case !== elem.toLowerCase()) {
  69 + camelCase = true;
  70 + }
  71 + copy[snake_case] = clone(obj[elem]);
  72 + }
  73 + return copy;
  74 + }
  75 + return obj;
  76 +}
  77 +
  78 +function convenienceClone (obj) {
  79 + camelCase = false;
  80 + obj = clone(obj) || {};
  81 + if (camelCase) {
  82 + obj.camel_case = true;
  83 + }
  84 + return obj;
  85 +}
  86 +
  87 +function callbackOrEmit (self, callback, err, res) {
  88 + if (callback) {
  89 + callback(err, res);
  90 + } else if (err) {
  91 + self.emit('error', err);
  92 + }
  93 +}
  94 +
  95 +function replyInOrder (self, callback, err, res, queue) {
  96 + // If the queue is explicitly passed, use that, otherwise fall back to the offline queue first,
  97 + // as there might be commands in both queues at the same time
  98 + var command_obj;
  99 + /* istanbul ignore if: TODO: Remove this as soon as we test Redis 3.2 on travis */
  100 + if (queue) {
  101 + command_obj = queue.peekBack();
  102 + } else {
  103 + command_obj = self.offline_queue.peekBack() || self.command_queue.peekBack();
  104 + }
  105 + if (!command_obj) {
  106 + process.nextTick(function () {
  107 + callbackOrEmit(self, callback, err, res);
  108 + });
  109 + } else {
  110 + var tmp = command_obj.callback;
  111 + command_obj.callback = tmp ?
  112 + function (e, r) {
  113 + tmp(e, r);
  114 + callbackOrEmit(self, callback, err, res);
  115 + } :
  116 + function (e, r) {
  117 + if (e) {
  118 + self.emit('error', e);
  119 + }
  120 + callbackOrEmit(self, callback, err, res);
  121 + };
  122 + }
  123 +}
  124 +
  125 +module.exports = {
  126 + reply_to_strings: replyToStrings,
  127 + reply_to_object: replyToObject,
  128 + print: print,
  129 + err_code: /^([A-Z]+)\s+(.+)$/,
  130 + monitor_regex: /^[0-9]{10,11}\.[0-9]+ \[[0-9]+ .+\]( ".+?")+$/,
  131 + clone: convenienceClone,
  132 + callback_or_emit: callbackOrEmit,
  133 + reply_in_order: replyInOrder
  134 +};
  1 +{
  2 + "_args": [
  3 + [
  4 + {
  5 + "raw": "redis",
  6 + "scope": null,
  7 + "escapedName": "redis",
  8 + "name": "redis",
  9 + "rawSpec": "",
  10 + "spec": "latest",
  11 + "type": "tag"
  12 + },
  13 + "/Users/fzy/project/koa2_Sequelize_project"
  14 + ]
  15 + ],
  16 + "_from": "redis@latest",
  17 + "_id": "redis@2.8.0",
  18 + "_inCache": true,
  19 + "_location": "/redis",
  20 + "_nodeVersion": "8.2.1",
  21 + "_npmOperationalInternal": {
  22 + "host": "s3://npm-registry-packages",
  23 + "tmp": "tmp/redis-2.8.0.tgz_1502169461811_0.2593278889544308"
  24 + },
  25 + "_npmUser": {
  26 + "name": "bridgear",
  27 + "email": "ruben@bridgewater.de"
  28 + },
  29 + "_npmVersion": "5.3.0",
  30 + "_phantomChildren": {},
  31 + "_requested": {
  32 + "raw": "redis",
  33 + "scope": null,
  34 + "escapedName": "redis",
  35 + "name": "redis",
  36 + "rawSpec": "",
  37 + "spec": "latest",
  38 + "type": "tag"
  39 + },
  40 + "_requiredBy": [
  41 + "#USER"
  42 + ],
  43 + "_resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz",
  44 + "_shasum": "202288e3f58c49f6079d97af7a10e1303ae14b02",
  45 + "_shrinkwrap": null,
  46 + "_spec": "redis",
  47 + "_where": "/Users/fzy/project/koa2_Sequelize_project",
  48 + "author": {
  49 + "name": "Matt Ranney",
  50 + "email": "mjr@ranney.com"
  51 + },
  52 + "bugs": {
  53 + "url": "https://github.com/NodeRedis/node_redis/issues"
  54 + },
  55 + "dependencies": {
  56 + "double-ended-queue": "^2.1.0-0",
  57 + "redis-commands": "^1.2.0",
  58 + "redis-parser": "^2.6.0"
  59 + },
  60 + "description": "Redis client library",
  61 + "devDependencies": {
  62 + "bluebird": "^3.0.2",
  63 + "coveralls": "^2.11.2",
  64 + "eslint": "^4.2.0",
  65 + "intercept-stdout": "~0.1.2",
  66 + "metrics": "^0.1.9",
  67 + "mocha": "^3.1.2",
  68 + "nyc": "^10.0.0",
  69 + "tcp-port-used": "^0.1.2",
  70 + "uuid": "^2.0.1",
  71 + "win-spawn": "^2.0.0"
  72 + },
  73 + "directories": {
  74 + "example": "examples",
  75 + "test": "test"
  76 + },
  77 + "dist": {
  78 + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==",
  79 + "shasum": "202288e3f58c49f6079d97af7a10e1303ae14b02",
  80 + "tarball": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz"
  81 + },
  82 + "engines": {
  83 + "node": ">=0.10.0"
  84 + },
  85 + "gitHead": "1380ad67a3f2b4a8b3dc31767e352e3bc4e63576",
  86 + "homepage": "https://github.com/NodeRedis/node_redis",
  87 + "keywords": [
  88 + "database",
  89 + "redis",
  90 + "transaction",
  91 + "pipelining",
  92 + "performance",
  93 + "queue",
  94 + "nodejs",
  95 + "pubsub",
  96 + "backpressure"
  97 + ],
  98 + "license": "MIT",
  99 + "main": "./index.js",
  100 + "maintainers": [
  101 + {
  102 + "name": "bridgear",
  103 + "email": "ruben@bridgewater.de"
  104 + },
  105 + {
  106 + "name": "bcoe",
  107 + "email": "ben@npmjs.com"
  108 + },
  109 + {
  110 + "name": "mjr",
  111 + "email": "mjr@ranney.com"
  112 + },
  113 + {
  114 + "name": "bryce",
  115 + "email": "bryce@ravenwall.com"
  116 + }
  117 + ],
  118 + "name": "redis",
  119 + "optionalDependencies": {},
  120 + "readme": "redis - a node.js redis client\n===========================\n\n[![Build Status](https://travis-ci.org/NodeRedis/node_redis.svg?branch=master)](https://travis-ci.org/NodeRedis/node_redis)\n[![Coverage Status](https://coveralls.io/repos/NodeRedis/node_redis/badge.svg?branch=)](https://coveralls.io/r/NodeRedis/node_redis?branch=)\n[![Windows Tests](https://img.shields.io/appveyor/ci/BridgeAR/node-redis/master.svg?label=Windows%20Tests)](https://ci.appveyor.com/project/BridgeAR/node-redis/branch/master)\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/NodeRedis/node_redis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)\n\nThis is a complete and feature rich Redis client for node.js. __It supports all\nRedis commands__ and focuses on high performance.\n\nInstall with:\n\n npm install redis\n\n## Usage Example\n\n```js\nvar redis = require(\"redis\"),\n client = redis.createClient();\n\n// if you'd like to select database 3, instead of 0 (default), call\n// client.select(3, function() { /* ... */ });\n\nclient.on(\"error\", function (err) {\n console.log(\"Error \" + err);\n});\n\nclient.set(\"string key\", \"string val\", redis.print);\nclient.hset(\"hash key\", \"hashtest 1\", \"some value\", redis.print);\nclient.hset([\"hash key\", \"hashtest 2\", \"some other value\"], redis.print);\nclient.hkeys(\"hash key\", function (err, replies) {\n console.log(replies.length + \" replies:\");\n replies.forEach(function (reply, i) {\n console.log(\" \" + i + \": \" + reply);\n });\n client.quit();\n});\n```\n\nThis will display:\n\n mjr:~/work/node_redis (master)$ node example.js\n Reply: OK\n Reply: 0\n Reply: 0\n 2 replies:\n 0: hashtest 1\n 1: hashtest 2\n mjr:~/work/node_redis (master)$\n\nNote that the API is entirely asynchronous. To get data back from the server,\nyou'll need to use a callback. From v.2.6 on the API supports camelCase and\nsnake_case and all options / variables / events etc. can be used either way. It\nis recommended to use camelCase as this is the default for the Node.js\nlandscape.\n\n### Promises\n\nYou can also use node_redis with promises by promisifying node_redis with\n[bluebird](https://github.com/petkaantonov/bluebird) as in:\n\n```js\nvar redis = require('redis');\nbluebird.promisifyAll(redis.RedisClient.prototype);\nbluebird.promisifyAll(redis.Multi.prototype);\n```\n\nIt'll add a *Async* to all node_redis functions (e.g. return client.getAsync().then())\n\n```js\n// We expect a value 'foo': 'bar' to be present\n// So instead of writing client.get('foo', cb); you have to write:\nreturn client.getAsync('foo').then(function(res) {\n console.log(res); // => 'bar'\n});\n\n// Using multi with promises looks like:\n\nreturn client.multi().get('foo').execAsync().then(function(res) {\n console.log(res); // => 'bar'\n});\n```\n\n### Sending Commands\n\nEach Redis command is exposed as a function on the `client` object.\nAll functions take either an `args` Array plus optional `callback` Function or\na variable number of individual arguments followed by an optional callback.\nExamples:\n\n```js\nclient.hmset([\"key\", \"test keys 1\", \"test val 1\", \"test keys 2\", \"test val 2\"], function (err, res) {});\n// Works the same as\nclient.hmset(\"key\", [\"test keys 1\", \"test val 1\", \"test keys 2\", \"test val 2\"], function (err, res) {});\n// Or\nclient.hmset(\"key\", \"test keys 1\", \"test val 1\", \"test keys 2\", \"test val 2\", function (err, res) {});\n```\n\nNote that in either form the `callback` is optional:\n\n```js\nclient.set(\"some key\", \"some val\");\nclient.set([\"some other key\", \"some val\"]);\n```\n\nIf the key is missing, reply will be null. Only if the [Redis Command\nReference](http://redis.io/commands) states something else it will not be null.\n\n```js\nclient.get(\"missingkey\", function(err, reply) {\n // reply is null when the key is missing\n console.log(reply);\n});\n```\n\nFor a list of Redis commands, see [Redis Command Reference](http://redis.io/commands)\n\nMinimal parsing is done on the replies. Commands that return a integer return\nJavaScript Numbers, arrays return JavaScript Array. `HGETALL` returns an Object\nkeyed by the hash keys. All strings will either be returned as string or as\nbuffer depending on your setting. Please be aware that sending null, undefined\nand Boolean values will result in the value coerced to a string!\n\n# Redis Commands\n\nThis library is a 1 to 1 mapping to [Redis commands](https://redis.io/commands).\nIt is not a cache library so please refer to Redis commands page for full usage\ndetails.\n\nExample setting key to auto expire using [SET command](https://redis.io/commands/set)\n\n```js\n// this key will expire after 10 seconds\nclient.set('key', 'value!', 'EX', 10);\n```\n\n# API\n\n## Connection and other Events\n\n`client` will emit some events about the state of the connection to the Redis server.\n\n### \"ready\"\n\n`client` will emit `ready` once a connection is established. Commands issued\nbefore the `ready` event are queued, then replayed just before this event is\nemitted.\n\n### \"connect\"\n\n`client` will emit `connect` as soon as the stream is connected to the server.\n\n### \"reconnecting\"\n\n`client` will emit `reconnecting` when trying to reconnect to the Redis server\nafter losing the connection. Listeners are passed an object containing `delay`\n(in ms) and `attempt` (the attempt #) attributes.\n\n### \"error\"\n\n`client` will emit `error` when encountering an error connecting to the Redis\nserver or when any other in node_redis occurs. If you use a command without\ncallback and encounter a ReplyError it is going to be emitted to the error\nlistener.\n\nSo please attach the error listener to node_redis.\n\n### \"end\"\n\n`client` will emit `end` when an established Redis server connection has closed.\n\n### \"drain\" (deprecated)\n\n`client` will emit `drain` when the TCP connection to the Redis server has been\nbuffering, but is now writable. This event can be used to stream commands in to\nRedis and adapt to backpressure.\n\nIf the stream is buffering `client.should_buffer` is set to true. Otherwise the\nvariable is always set to false. That way you can decide when to reduce your\nsend rate and resume sending commands when you get `drain`.\n\nYou can also check the return value of each command as it will also return the\nbackpressure indicator (deprecated). If false is returned the stream had to\nbuffer.\n\n### \"warning\"\n\n`client` will emit `warning` when password was set but none is needed and if a\ndeprecated option / function / similar is used.\n\n### \"idle\" (deprecated)\n\n`client` will emit `idle` when there are no outstanding commands that are\nawaiting a response.\n\n## redis.createClient()\nIf you have `redis-server` running on the same machine as node, then the\ndefaults for port and host are probably fine and you don't need to supply any\narguments. `createClient()` returns a `RedisClient` object. Otherwise,\n`createClient()` accepts these arguments:\n\n* `redis.createClient([options])`\n* `redis.createClient(unix_socket[, options])`\n* `redis.createClient(redis_url[, options])`\n* `redis.createClient(port[, host][, options])`\n\n__Tip:__ If the Redis server runs on the same machine as the client consider\nusing unix sockets if possible to increase throughput.\n\n#### `options` object properties\n| Property | Default | Description |\n|-----------|-----------|-------------|\n| host | 127.0.0.1 | IP address of the Redis server |\n| port | 6379 | Port of the Redis server |\n| path | null | The UNIX socket string of the Redis server |\n| url | null | The URL of the Redis server. Format: `[redis:]//[[user][:password@]][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]` (More info avaliable at [IANA](http://www.iana.org/assignments/uri-schemes/prov/redis)). |\n| parser | javascript | __Deprecated__ Use either the built-in JS parser [`javascript`]() or the native [`hiredis`]() parser. __Note__ `node_redis` < 2.6 uses hiredis as default if installed. This changed in v.2.6.0. |\n| string_numbers | null | Set to `true`, `node_redis` will return Redis number values as Strings instead of javascript Numbers. Useful if you need to handle big numbers (above `Number.MAX_SAFE_INTEGER === 2^53`). Hiredis is incapable of this behavior, so setting this option to `true` will result in the built-in javascript parser being used no matter the value of the `parser` option. |\n| return_buffers | false | If set to `true`, then all replies will be sent to callbacks as Buffers instead of Strings. |\n| detect_buffers | false | If set to `true`, then replies will be sent to callbacks as Buffers. This option lets you switch between Buffers and Strings on a per-command basis, whereas `return_buffers` applies to every command on a client. __Note__: This doesn't work properly with the pubsub mode. A subscriber has to either always return Strings or Buffers. |\n| socket_keepalive | true | If set to `true`, the keep-alive functionality is enabled on the underlying socket. |\n| no_ready_check | false | When a connection is established to the Redis server, the server might still be loading the database from disk. While loading, the server will not respond to any commands. To work around this, `node_redis` has a \"ready check\" which sends the `INFO` command to the server. The response from the `INFO` command indicates whether the server is ready for more commands. When ready, `node_redis` emits a `ready` event. Setting `no_ready_check` to `true` will inhibit this check. |\n| enable_offline_queue | true | By default, if there is no active connection to the Redis server, commands are added to a queue and are executed once the connection has been established. Setting `enable_offline_queue` to `false` will disable this feature and the callback will be executed immediately with an error, or an error will be emitted if no callback is specified. |\n| retry_max_delay | null | __Deprecated__ _Please use `retry_strategy` instead._ By default, every time the client tries to connect and fails, the reconnection delay almost doubles. This delay normally grows infinitely, but setting `retry_max_delay` limits it to the maximum value provided in milliseconds. |\n| connect_timeout | 3600000 | __Deprecated__ _Please use `retry_strategy` instead._ Setting `connect_timeout` limits the total time for the client to connect and reconnect. The value is provided in milliseconds and is counted from the moment a new client is created or from the time the connection is lost. The last retry is going to happen exactly at the timeout time. Default is to try connecting until the default system socket timeout has been exceeded and to try reconnecting until 1h has elapsed. |\n| max_attempts | 0 | __Deprecated__ _Please use `retry_strategy` instead._ By default, a client will try reconnecting until connected. Setting `max_attempts` limits total amount of connection attempts. Setting this to 1 will prevent any reconnect attempt. |\n| retry_unfulfilled_commands | false | If set to `true`, all commands that were unfulfilled while the connection is lost will be retried after the connection has been reestablished. Use this with caution if you use state altering commands (e.g. `incr`). This is especially useful if you use blocking commands. |\n| password | null | If set, client will run Redis auth command on connect. Alias `auth_pass` __Note__ `node_redis` < 2.5 must use `auth_pass` |\n| db | null | If set, client will run Redis `select` command on connect. |\n| family | IPv4 | You can force using IPv6 if you set the family to 'IPv6'. See Node.js [net](https://nodejs.org/api/net.html) or [dns](https://nodejs.org/api/dns.html) modules on how to use the family type. |\n| disable_resubscribing | false | If set to `true`, a client won't resubscribe after disconnecting. |\n| rename_commands | null | Passing an object with renamed commands to use instead of the original functions. For example, if you renamed the command KEYS to \"DO-NOT-USE\" then the rename_commands object would be: `{ KEYS : \"DO-NOT-USE\" }` . See the [Redis security topics](http://redis.io/topics/security) for more info. |\n| tls | null | An object containing options to pass to [tls.connect](http://nodejs.org/api/tls.html#tls_tls_connect_port_host_options_callback) to set up a TLS connection to Redis (if, for example, it is set up to be accessible via a tunnel). |\n| prefix | null | A string used to prefix all used keys (e.g. `namespace:test`). Please be aware that the `keys` command will not be prefixed. The `keys` command has a \"pattern\" as argument and no key and it would be impossible to determine the existing keys in Redis if this would be prefixed. |\n| retry_strategy | function | A function that receives an options object as parameter including the retry `attempt`, the `total_retry_time` indicating how much time passed since the last time connected, the `error` why the connection was lost and the number of `times_connected` in total. If you return a number from this function, the retry will happen exactly after that time in milliseconds. If you return a non-number, no further retry will happen and all offline commands are flushed with errors. Return an error to return that specific error to all offline commands. Example below. |\n\n```js\nvar redis = require(\"redis\");\nvar client = redis.createClient({detect_buffers: true});\n\nclient.set(\"foo_rand000000000000\", \"OK\");\n\n// This will return a JavaScript String\nclient.get(\"foo_rand000000000000\", function (err, reply) {\n console.log(reply.toString()); // Will print `OK`\n});\n\n// This will return a Buffer since original key is specified as a Buffer\nclient.get(new Buffer(\"foo_rand000000000000\"), function (err, reply) {\n console.log(reply.toString()); // Will print `<Buffer 4f 4b>`\n});\nclient.quit();\n```\n\nretry_strategy example\n\n```js\nvar client = redis.createClient({\n retry_strategy: function (options) {\n if (options.error && options.error.code === 'ECONNREFUSED') {\n // End reconnecting on a specific error and flush all commands with\n // a individual error\n return new Error('The server refused the connection');\n }\n if (options.total_retry_time > 1000 * 60 * 60) {\n // End reconnecting after a specific timeout and flush all commands\n // with a individual error\n return new Error('Retry time exhausted');\n }\n if (options.attempt > 10) {\n // End reconnecting with built in error\n return undefined;\n }\n // reconnect after\n return Math.min(options.attempt * 100, 3000);\n }\n});\n```\n\n## client.auth(password[, callback])\n\nWhen connecting to a Redis server that requires authentication, the `AUTH`\ncommand must be sent as the first command after connecting. This can be tricky\nto coordinate with reconnections, the ready check, etc. To make this easier,\n`client.auth()` stashes `password` and will send it after each connection,\nincluding reconnections. `callback` is invoked only once, after the response to\nthe very first `AUTH` command sent.\nNOTE: Your call to `client.auth()` should not be inside the ready handler. If\nyou are doing this wrong, `client` will emit an error that looks\nsomething like this `Error: Ready check failed: ERR operation not permitted`.\n\n## backpressure\n\n### stream\n\nThe client exposed the used [stream](https://nodejs.org/api/stream.html) in\n`client.stream` and if the stream or client had to\n[buffer](https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback)\nthe command in `client.should_buffer`. In combination this can be used to\nimplement backpressure by checking the buffer state before sending a command and\nlistening to the stream\n[drain](https://nodejs.org/api/stream.html#stream_event_drain) event.\n\n## client.quit()\n\nThis sends the quit command to the redis server and ends cleanly right after all\nrunning commands were properly handled. If this is called while reconnecting\n(and therefore no connection to the redis server exists) it is going to end the\nconnection right away instead of resulting in further reconnections! All offline\ncommands are going to be flushed with an error in that case.\n\n## client.end(flush)\n\nForcibly close the connection to the Redis server. Note that this does not wait\nuntil all replies have been parsed. If you want to exit cleanly, call\n`client.quit()` as mentioned above.\n\nYou should set flush to true, if you are not absolutely sure you do not care\nabout any other commands. If you set flush to false all still running commands\nwill silently fail.\n\nThis example closes the connection to the Redis server before the replies have\nbeen read. You probably don't want to do this:\n\n```js\nvar redis = require(\"redis\"),\n client = redis.createClient();\n\nclient.set(\"foo_rand000000000000\", \"some fantastic value\", function (err, reply) {\n // This will either result in an error (flush parameter is set to true)\n // or will silently fail and this callback will not be called at all (flush set to false)\n console.log(err);\n});\nclient.end(true); // No further commands will be processed\nclient.get(\"foo_rand000000000000\", function (err, reply) {\n console.log(err); // => 'The connection has already been closed.'\n});\n```\n\n`client.end()` without the flush parameter set to true should NOT be used in production!\n\n## Error handling (>= v.2.6)\n\nCurrently the following error subclasses exist:\n\n* `RedisError`: _All errors_ returned by the client\n* `ReplyError` subclass of `RedisError`: All errors returned by __Redis__ itself\n* `AbortError` subclass of `RedisError`: All commands that could not finish due\n to what ever reason\n* `ParserError` subclass of `RedisError`: Returned in case of a parser error\n (this should not happen)\n* `AggregateError` subclass of `AbortError`: Emitted in case multiple unresolved\n commands without callback got rejected in debug_mode instead of lots of\n `AbortError`s.\n\nAll error classes are exported by the module.\n\nExample:\n```js\nvar redis = require('./');\nvar assert = require('assert');\nvar client = redis.createClient();\n\nclient.on('error', function (err) {\n assert(err instanceof Error);\n assert(err instanceof redis.AbortError);\n assert(err instanceof redis.AggregateError);\n // The set and get get aggregated in here\n assert.strictEqual(err.errors.length, 2);\n assert.strictEqual(err.code, 'NR_CLOSED');\n});\nclient.set('foo', 123, 'bar', function (err, res) { // Too many arguments\n assert(err instanceof redis.ReplyError); // => true\n assert.strictEqual(err.command, 'SET');\n assert.deepStrictEqual(err.args, ['foo', 123, 'bar']);\n\n redis.debug_mode = true;\n client.set('foo', 'bar');\n client.get('foo');\n process.nextTick(function () {\n // Force closing the connection while the command did not yet return\n client.end(true);\n redis.debug_mode = false;\n });\n});\n\n```\n\nEvery `ReplyError` contains the `command` name in all-caps and the arguments (`args`).\n\nIf node_redis emits a library error because of another error, the triggering\nerror is added to the returned error as `origin` attribute.\n\n___Error codes___\n\nnode_redis returns a `NR_CLOSED` error code if the clients connection dropped.\nIf a command unresolved command got rejected a `UNCERTAIN_STATE` code is\nreturned. A `CONNECTION_BROKEN` error code is used in case node_redis gives up\nto reconnect.\n\n## client.unref()\n\nCall `unref()` on the underlying socket connection to the Redis server, allowing\nthe program to exit once no more commands are pending.\n\nThis is an **experimental** feature, and only supports a subset of the Redis\nprotocol. Any commands where client state is saved on the Redis server, e.g.\n`*SUBSCRIBE` or the blocking `BL*` commands will *NOT* work with `.unref()`.\n\n```js\nvar redis = require(\"redis\")\nvar client = redis.createClient()\n\n/*\n Calling unref() will allow this program to exit immediately after the get\n command finishes. Otherwise the client would hang as long as the\n client-server connection is alive.\n*/\nclient.unref()\nclient.get(\"foo\", function (err, value){\n if (err) throw(err)\n console.log(value)\n})\n```\n\n## Friendlier hash commands\n\nMost Redis commands take a single String or an Array of Strings as arguments,\nand replies are sent back as a single String or an Array of Strings. When\ndealing with hash values, there are a couple of useful exceptions to this.\n\n### client.hgetall(hash, callback)\n\nThe reply from an HGETALL command will be converted into a JavaScript Object by\n`node_redis`. That way you can interact with the responses using JavaScript\nsyntax.\n\nExample:\n\n```js\nclient.hmset(\"hosts\", \"mjr\", \"1\", \"another\", \"23\", \"home\", \"1234\");\nclient.hgetall(\"hosts\", function (err, obj) {\n console.dir(obj);\n});\n```\n\nOutput:\n\n```js\n{ mjr: '1', another: '23', home: '1234' }\n```\n\n### client.hmset(hash, obj[, callback])\n\nMultiple values in a hash can be set by supplying an object:\n\n```js\nclient.HMSET(key2, {\n \"0123456789\": \"abcdefghij\", // NOTE: key and value will be coerced to strings\n \"some manner of key\": \"a type of value\"\n});\n```\n\nThe properties and values of this Object will be set as keys and values in the\nRedis hash.\n\n### client.hmset(hash, key1, val1, ... keyn, valn, [callback])\n\nMultiple values may also be set by supplying a list:\n\n```js\nclient.HMSET(key1, \"0123456789\", \"abcdefghij\", \"some manner of key\", \"a type of value\");\n```\n\n## Publish / Subscribe\n\nExample of the publish / subscribe API. This program opens two\nclient connections, subscribes to a channel on one of them, and publishes to that\nchannel on the other:\n\n```js\nvar redis = require(\"redis\");\nvar sub = redis.createClient(), pub = redis.createClient();\nvar msg_count = 0;\n\nsub.on(\"subscribe\", function (channel, count) {\n pub.publish(\"a nice channel\", \"I am sending a message.\");\n pub.publish(\"a nice channel\", \"I am sending a second message.\");\n pub.publish(\"a nice channel\", \"I am sending my last message.\");\n});\n\nsub.on(\"message\", function (channel, message) {\n console.log(\"sub channel \" + channel + \": \" + message);\n msg_count += 1;\n if (msg_count === 3) {\n sub.unsubscribe();\n sub.quit();\n pub.quit();\n }\n});\n\nsub.subscribe(\"a nice channel\");\n```\n\nWhen a client issues a `SUBSCRIBE` or `PSUBSCRIBE`, that connection is put into\na \"subscriber\" mode. At that point, only commands that modify the subscription\nset are valid and quit (and depending on the redis version ping as well). When\nthe subscription set is empty, the connection is put back into regular mode.\n\nIf you need to send regular commands to Redis while in subscriber mode, just\nopen another connection with a new client (hint: use `client.duplicate()`).\n\n## Subscriber Events\n\nIf a client has subscriptions active, it may emit these events:\n\n### \"message\" (channel, message)\n\nClient will emit `message` for every message received that matches an active subscription.\nListeners are passed the channel name as `channel` and the message as `message`.\n\n### \"pmessage\" (pattern, channel, message)\n\nClient will emit `pmessage` for every message received that matches an active\nsubscription pattern. Listeners are passed the original pattern used with\n`PSUBSCRIBE` as `pattern`, the sending channel name as `channel`, and the\nmessage as `message`.\n\n### \"message_buffer\" (channel, message)\n\nThis is the same as the `message` event with the exception, that it is always\ngoing to emit a buffer. If you listen to the `message` event at the same time as\nthe `message_buffer`, it is always going to emit a string.\n\n### \"pmessage_buffer\" (pattern, channel, message)\n\nThis is the same as the `pmessage` event with the exception, that it is always\ngoing to emit a buffer. If you listen to the `pmessage` event at the same time\nas the `pmessage_buffer`, it is always going to emit a string.\n\n### \"subscribe\" (channel, count)\n\nClient will emit `subscribe` in response to a `SUBSCRIBE` command. Listeners are\npassed the channel name as `channel` and the new count of subscriptions for this\nclient as `count`.\n\n### \"psubscribe\" (pattern, count)\n\nClient will emit `psubscribe` in response to a `PSUBSCRIBE` command. Listeners\nare passed the original pattern as `pattern`, and the new count of subscriptions\nfor this client as `count`.\n\n### \"unsubscribe\" (channel, count)\n\nClient will emit `unsubscribe` in response to a `UNSUBSCRIBE` command. Listeners\nare passed the channel name as `channel` and the new count of subscriptions for\nthis client as `count`. When `count` is 0, this client has left subscriber mode\nand no more subscriber events will be emitted.\n\n### \"punsubscribe\" (pattern, count)\n\nClient will emit `punsubscribe` in response to a `PUNSUBSCRIBE` command.\nListeners are passed the channel name as `channel` and the new count of\nsubscriptions for this client as `count`. When `count` is 0, this client has\nleft subscriber mode and no more subscriber events will be emitted.\n\n## client.multi([commands])\n\n`MULTI` commands are queued up until an `EXEC` is issued, and then all commands\nare run atomically by Redis. The interface in `node_redis` is to return an\nindividual `Multi` object by calling `client.multi()`. If any command fails to\nqueue, all commands are rolled back and none is going to be executed (For\nfurther information look at\n[transactions](http://redis.io/topics/transactions)).\n\n```js\nvar redis = require(\"./index\"),\n client = redis.createClient(), set_size = 20;\n\nclient.sadd(\"bigset\", \"a member\");\nclient.sadd(\"bigset\", \"another member\");\n\nwhile (set_size > 0) {\n client.sadd(\"bigset\", \"member \" + set_size);\n set_size -= 1;\n}\n\n// multi chain with an individual callback\nclient.multi()\n .scard(\"bigset\")\n .smembers(\"bigset\")\n .keys(\"*\", function (err, replies) {\n // NOTE: code in this callback is NOT atomic\n // this only happens after the the .exec call finishes.\n client.mget(replies, redis.print);\n })\n .dbsize()\n .exec(function (err, replies) {\n console.log(\"MULTI got \" + replies.length + \" replies\");\n replies.forEach(function (reply, index) {\n console.log(\"Reply \" + index + \": \" + reply.toString());\n });\n });\n```\n\n### Multi.exec([callback])\n\n`client.multi()` is a constructor that returns a `Multi` object. `Multi` objects\nshare all of the same command methods as `client` objects do. Commands are\nqueued up inside the `Multi` object until `Multi.exec()` is invoked.\n\nIf your code contains an syntax error an EXECABORT error is going to be thrown\nand all commands are going to be aborted. That error contains a `.errors`\nproperty that contains the concrete errors.\nIf all commands were queued successfully and an error is thrown by redis while\nprocessing the commands that error is going to be returned in the result array!\nNo other command is going to be aborted though than the onces failing.\n\nYou can either chain together `MULTI` commands as in the above example, or you\ncan queue individual commands while still sending regular client command as in\nthis example:\n\n```js\nvar redis = require(\"redis\"),\n client = redis.createClient(), multi;\n\n// start a separate multi command queue\nmulti = client.multi();\nmulti.incr(\"incr thing\", redis.print);\nmulti.incr(\"incr other thing\", redis.print);\n\n// runs immediately\nclient.mset(\"incr thing\", 100, \"incr other thing\", 1, redis.print);\n\n// drains multi queue and runs atomically\nmulti.exec(function (err, replies) {\n console.log(replies); // 101, 2\n});\n```\n\nIn addition to adding commands to the `MULTI` queue individually, you can also\npass an array of commands and arguments to the constructor:\n\n```js\nvar redis = require(\"redis\"),\n client = redis.createClient(), multi;\n\nclient.multi([\n [\"mget\", \"multifoo\", \"multibar\", redis.print],\n [\"incr\", \"multifoo\"],\n [\"incr\", \"multibar\"]\n]).exec(function (err, replies) {\n console.log(replies);\n});\n```\n\n### Multi.exec_atomic([callback])\n\nIdentical to Multi.exec but with the difference that executing a single command\nwill not use transactions.\n\n## client.batch([commands])\n\nIdentical to .multi without transactions. This is recommended if you want to\nexecute many commands at once but don't have to rely on transactions.\n\n`BATCH` commands are queued up until an `EXEC` is issued, and then all commands\nare run atomically by Redis. The interface in `node_redis` is to return an\nindividual `Batch` object by calling `client.batch()`. The only difference\nbetween .batch and .multi is that no transaction is going to be used.\nBe aware that the errors are - just like in multi statements - in the result.\nOtherwise both, errors and results could be returned at the same time.\n\nIf you fire many commands at once this is going to boost the execution speed\nsignificantly compared to firing the same commands in a loop without waiting for\nthe result! See the benchmarks for further comparison. Please remember that all\ncommands are kept in memory until they are fired.\n\n## Monitor mode\n\nRedis supports the `MONITOR` command, which lets you see all commands received\nby the Redis server across all client connections, including from other client\nlibraries and other computers.\n\nA `monitor` event is going to be emitted for every command fired from any client\nconnected to the server including the monitoring client itself. The callback for\nthe `monitor` event takes a timestamp from the Redis server, an array of command\narguments and the raw monitoring string.\n\nExample:\n\n```js\nvar client = require(\"redis\").createClient();\nclient.monitor(function (err, res) {\n console.log(\"Entering monitoring mode.\");\n});\nclient.set('foo', 'bar');\n\nclient.on(\"monitor\", function (time, args, raw_reply) {\n console.log(time + \": \" + args); // 1458910076.446514:['set', 'foo', 'bar']\n});\n```\n\n# Extras\n\nSome other things you might like to know about.\n\n## client.server_info\n\nAfter the ready probe completes, the results from the INFO command are saved in\nthe `client.server_info` object.\n\nThe `versions` key contains an array of the elements of the version string for\neasy comparison.\n\n > client.server_info.redis_version\n '2.3.0'\n > client.server_info.versions\n [ 2, 3, 0 ]\n\n## redis.print()\n\nA handy callback function for displaying return values when testing. Example:\n\n```js\nvar redis = require(\"redis\"),\n client = redis.createClient();\n\nclient.on(\"connect\", function () {\n client.set(\"foo_rand000000000000\", \"some fantastic value\", redis.print);\n client.get(\"foo_rand000000000000\", redis.print);\n});\n```\n\nThis will print:\n\n Reply: OK\n Reply: some fantastic value\n\nNote that this program will not exit cleanly because the client is still connected.\n\n## Multi-word commands\n\nTo execute redis multi-word commands like `SCRIPT LOAD` or `CLIENT LIST` pass\nthe second word as first parameter:\n\n client.script('load', 'return 1');\n client.multi().script('load', 'return 1').exec(...);\n client.multi([['script', 'load', 'return 1']]).exec(...);\n\n## client.duplicate([options][, callback])\n\nDuplicate all current options and return a new redisClient instance. All options\npassed to the duplicate function are going to replace the original option. If\nyou pass a callback, duplicate is going to wait until the client is ready and\nreturns it in the callback. If an error occurs in the meanwhile, that is going\nto return an error instead in the callback.\n\nOne example of when to use duplicate() would be to accommodate the connection-\nblocking redis commands BRPOP, BLPOP, and BRPOPLPUSH. If these commands\nare used on the same redisClient instance as non-blocking commands, the\nnon-blocking ones may be queued up until after the blocking ones finish.\n\n var Redis=require('redis');\n var client = Redis.createClient();\n var clientBlocking = client.duplicate();\n\n var get = function() {\n console.log(\"get called\");\n client.get(\"any_key\",function() { console.log(\"get returned\"); });\n setTimeout( get, 1000 );\n };\n var brpop = function() {\n console.log(\"brpop called\");\n clientBlocking.brpop(\"nonexistent\", 5, function() {\n console.log(\"brpop return\");\n setTimeout( brpop, 1000 );\n });\n };\n get();\n brpop();\n\nAnother reason to use duplicate() is when multiple DBs on the same server are\naccessed via the redis SELECT command. Each DB could use its own connection.\n\n## client.send_command(command_name[, [args][, callback]])\n\nAll Redis commands have been added to the `client` object. However, if new\ncommands are introduced before this library is updated or if you want to add\nindividual commands you can use `send_command()` to send arbitrary commands to\nRedis.\n\nAll commands are sent as multi-bulk commands. `args` can either be an Array of\narguments, or omitted / set to undefined.\n\n## client.add_command(command_name)\n\nCalling add_command will add a new command to the prototype. The exact command\nname will be used when calling using this new command. Using arbitrary arguments\nis possible as with any other command.\n\n## client.connected\n\nBoolean tracking the state of the connection to the Redis server.\n\n## client.command_queue_length\n\nThe number of commands that have been sent to the Redis server but not yet\nreplied to. You can use this to enforce some kind of maximum queue depth for\ncommands while connected.\n\n## client.offline_queue_length\n\nThe number of commands that have been queued up for a future connection. You can\nuse this to enforce some kind of maximum queue depth for pre-connection\ncommands.\n\n### Commands with Optional and Keyword arguments\n\nThis applies to anything that uses an optional `[WITHSCORES]` or `[LIMIT offset\ncount]` in the [redis.io/commands](http://redis.io/commands) documentation.\n\nExample:\n\n```js\nvar args = [ 'myzset', 1, 'one', 2, 'two', 3, 'three', 99, 'ninety-nine' ];\nclient.zadd(args, function (err, response) {\n if (err) throw err;\n console.log('added '+response+' items.');\n\n // -Infinity and +Infinity also work\n var args1 = [ 'myzset', '+inf', '-inf' ];\n client.zrevrangebyscore(args1, function (err, response) {\n if (err) throw err;\n console.log('example1', response);\n // write your code here\n });\n\n var max = 3, min = 1, offset = 1, count = 2;\n var args2 = [ 'myzset', max, min, 'WITHSCORES', 'LIMIT', offset, count ];\n client.zrevrangebyscore(args2, function (err, response) {\n if (err) throw err;\n console.log('example2', response);\n // write your code here\n });\n});\n```\n\n## Performance\n\nMuch effort has been spent to make `node_redis` as fast as possible for common\noperations.\n\n```\nLenovo T450s, i7-5600U and 12gb memory\nclients: 1, NodeJS: 6.2.0, Redis: 3.2.0, parser: javascript, connected by: tcp\n PING, 1/1 avg/max: 0.02/ 5.26 2501ms total, 46916 ops/sec\n PING, batch 50/1 avg/max: 0.06/ 4.35 2501ms total, 755178 ops/sec\n SET 4B str, 1/1 avg/max: 0.02/ 4.75 2501ms total, 40856 ops/sec\n SET 4B str, batch 50/1 avg/max: 0.11/ 1.51 2501ms total, 432727 ops/sec\n SET 4B buf, 1/1 avg/max: 0.05/ 2.76 2501ms total, 20659 ops/sec\n SET 4B buf, batch 50/1 avg/max: 0.25/ 1.76 2501ms total, 194962 ops/sec\n GET 4B str, 1/1 avg/max: 0.02/ 1.55 2501ms total, 45156 ops/sec\n GET 4B str, batch 50/1 avg/max: 0.09/ 3.15 2501ms total, 524110 ops/sec\n GET 4B buf, 1/1 avg/max: 0.02/ 3.07 2501ms total, 44563 ops/sec\n GET 4B buf, batch 50/1 avg/max: 0.10/ 3.18 2501ms total, 473171 ops/sec\n SET 4KiB str, 1/1 avg/max: 0.03/ 1.54 2501ms total, 32627 ops/sec\n SET 4KiB str, batch 50/1 avg/max: 0.34/ 1.89 2501ms total, 146861 ops/sec\n SET 4KiB buf, 1/1 avg/max: 0.05/ 2.85 2501ms total, 20688 ops/sec\n SET 4KiB buf, batch 50/1 avg/max: 0.36/ 1.83 2501ms total, 138165 ops/sec\n GET 4KiB str, 1/1 avg/max: 0.02/ 1.37 2501ms total, 39389 ops/sec\n GET 4KiB str, batch 50/1 avg/max: 0.24/ 1.81 2501ms total, 208157 ops/sec\n GET 4KiB buf, 1/1 avg/max: 0.02/ 2.63 2501ms total, 39918 ops/sec\n GET 4KiB buf, batch 50/1 avg/max: 0.31/ 8.56 2501ms total, 161575 ops/sec\n INCR, 1/1 avg/max: 0.02/ 4.69 2501ms total, 45685 ops/sec\n INCR, batch 50/1 avg/max: 0.09/ 3.06 2501ms total, 539964 ops/sec\n LPUSH, 1/1 avg/max: 0.02/ 3.04 2501ms total, 41253 ops/sec\n LPUSH, batch 50/1 avg/max: 0.12/ 1.94 2501ms total, 425090 ops/sec\n LRANGE 10, 1/1 avg/max: 0.02/ 2.28 2501ms total, 39850 ops/sec\n LRANGE 10, batch 50/1 avg/max: 0.25/ 1.85 2501ms total, 194302 ops/sec\n LRANGE 100, 1/1 avg/max: 0.05/ 2.93 2501ms total, 21026 ops/sec\n LRANGE 100, batch 50/1 avg/max: 1.52/ 2.89 2501ms total, 32767 ops/sec\n SET 4MiB str, 1/1 avg/max: 5.16/ 15.55 2502ms total, 193 ops/sec\n SET 4MiB str, batch 20/1 avg/max: 89.73/ 99.96 2513ms total, 223 ops/sec\n SET 4MiB buf, 1/1 avg/max: 2.23/ 8.35 2501ms total, 446 ops/sec\n SET 4MiB buf, batch 20/1 avg/max: 41.47/ 50.91 2530ms total, 482 ops/sec\n GET 4MiB str, 1/1 avg/max: 2.79/ 10.91 2502ms total, 358 ops/sec\n GET 4MiB str, batch 20/1 avg/max: 101.61/118.11 2541ms total, 197 ops/sec\n GET 4MiB buf, 1/1 avg/max: 2.32/ 14.93 2502ms total, 430 ops/sec\n GET 4MiB buf, batch 20/1 avg/max: 65.01/ 84.72 2536ms total, 308 ops/sec\n ```\n\n## Debugging\n\nTo get debug output run your `node_redis` application with `NODE_DEBUG=redis`.\n\nThis is also going to result in good stack traces opposed to useless ones\notherwise for any async operation.\nIf you only want to have good stack traces but not the debug output run your\napplication in development mode instead (`NODE_ENV=development`).\n\nGood stack traces are only activated in development and debug mode as this\nresults in a significant performance penalty.\n\n___Comparison___:\nUseless stack trace:\n```\nReplyError: ERR wrong number of arguments for 'set' command\n at parseError (/home/ruben/repos/redis/node_modules/redis-parser/lib/parser.js:158:12)\n at parseType (/home/ruben/repos/redis/node_modules/redis-parser/lib/parser.js:219:14)\n```\nGood stack trace:\n```\nReplyError: ERR wrong number of arguments for 'set' command\n at new Command (/home/ruben/repos/redis/lib/command.js:9:902)\n at RedisClient.set (/home/ruben/repos/redis/lib/commands.js:9:3238)\n at Context.<anonymous> (/home/ruben/repos/redis/test/good_stacks.spec.js:20:20)\n at callFnAsync (/home/ruben/repos/redis/node_modules/mocha/lib/runnable.js:349:8)\n at Test.Runnable.run (/home/ruben/repos/redis/node_modules/mocha/lib/runnable.js:301:7)\n at Runner.runTest (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:422:10)\n at /home/ruben/repos/redis/node_modules/mocha/lib/runner.js:528:12\n at next (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:342:14)\n at /home/ruben/repos/redis/node_modules/mocha/lib/runner.js:352:7\n at next (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:284:14)\n at Immediate._onImmediate (/home/ruben/repos/redis/node_modules/mocha/lib/runner.js:320:5)\n at processImmediate [as _immediateCallback] (timers.js:383:17)\n```\n\n## How to Contribute\n- Open a pull request or an issue about what you want to implement / change. We're glad for any help!\n - Please be aware that we'll only accept fully tested code.\n\n## Contributors\n\nThe original author of node_redis is [Matthew Ranney](https://github.com/mranney)\n\nThe current lead maintainer is [Ruben Bridgewater](https://github.com/BridgeAR)\n\nMany [others](https://github.com/NodeRedis/node_redis/graphs/contributors)\ncontributed to `node_redis` too. Thanks to all of them!\n\n## License\n\n[MIT](LICENSE)\n\n### Consolidation: It's time for celebration\n\nRight now there are two great redis clients around and both have some advantages\nabove each other. We speak about ioredis and node_redis. So after talking to\neach other about how we could improve in working together we (that is @luin and\n@BridgeAR) decided to work towards a single library on the long run. But step by\nstep.\n\nFirst of all, we want to split small parts of our libraries into others so that\nwe're both able to use the same code. Those libraries are going to be maintained\nunder the NodeRedis organization. This is going to reduce the maintenance\noverhead, allows others to use the very same code, if they need it and it's way\neasyer for others to contribute to both libraries.\n\nWe're very happy about this step towards working together as we both want to\ngive you the best redis experience possible.\n\nIf you want to join our cause by help maintaining something, please don't\nhesitate to contact either one of us.\n",
  121 + "readmeFilename": "README.md",
  122 + "repository": {
  123 + "type": "git",
  124 + "url": "git://github.com/NodeRedis/node_redis.git"
  125 + },
  126 + "scripts": {
  127 + "benchmark": "node benchmarks/multi_bench.js",
  128 + "compare": "node benchmarks/diff_multi_bench_output.js beforeBench.txt afterBench.txt",
  129 + "coverage": "nyc report --reporter=html",
  130 + "coveralls": "nyc report --reporter=text-lcov | coveralls",
  131 + "lint": "eslint . --fix && npm run coverage",
  132 + "test": "nyc --cache mocha ./test/*.js ./test/commands/*.js --timeout=8000"
  133 + },
  134 + "version": "2.8.0"
  135 +}
@@ -4,6 +4,10 @@ var status = require('../util/resTemplate') @@ -4,6 +4,10 @@ var status = require('../util/resTemplate')
4 4
5 router.prefix('/users') 5 router.prefix('/users')
6 6
  7 +
  8 +/**
  9 + * 添加用户
  10 + */
7 router.post('/addUser',async (ctx, next) => { 11 router.post('/addUser',async (ctx, next) => {
8 try{ 12 try{
9 var data = await userContoller.addUser(ctx, next); 13 var data = await userContoller.addUser(ctx, next);
@@ -12,6 +16,11 @@ router.post('/addUser',async (ctx, next) => { @@ -12,6 +16,11 @@ router.post('/addUser',async (ctx, next) => {
12 status.catchError(400,e.message); 16 status.catchError(400,e.message);
13 } 17 }
14 }) 18 })
  19 +
  20 +
  21 +/**
  22 + * 用户登录
  23 + */
15 router.post('/login',async(ctx, next)=>{ 24 router.post('/login',async(ctx, next)=>{
16 try{ 25 try{
17 var data = await userContoller.login(ctx, next); 26 var data = await userContoller.login(ctx, next);
@@ -21,6 +30,18 @@ router.post('/login',async(ctx, next)=>{ @@ -21,6 +30,18 @@ router.post('/login',async(ctx, next)=>{
21 } 30 }
22 }) 31 })
23 32
  33 +router.get('/authLogin.html', async function(ctx, next){
  34 + let type = ctx.request.query.type;
  35 + if(type == "GITHUB"){
  36 + await ctx.redirect(`https://github.com/login/oauth/authorize?client_id=${config.github.client_id}&state=${Date.now()}&redirect_uri=${config.host}${config.github.redirect_url}`);
  37 + }else{
  38 + await ctx.redirect(`https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=${config.qq.appId}&state=${Date.now()}&redirect_uri=${config.host}${h}`);
  39 + }
  40 +});
  41 +
  42 +/**
  43 + *获取用户列表
  44 + */
24 router.get('/getStu/:type',async(ctx, next)=>{ 45 router.get('/getStu/:type',async(ctx, next)=>{
25 try{ 46 try{
26 var data = await userContoller.getStu(ctx, next); 47 var data = await userContoller.getStu(ctx, next);
@@ -30,6 +51,9 @@ router.get('/getStu/:type',async(ctx, next)=>{ @@ -30,6 +51,9 @@ router.get('/getStu/:type',async(ctx, next)=>{
30 } 51 }
31 }) 52 })
32 53
  54 +/**
  55 + * 修改用户信息
  56 + */
33 router.put('/updateUserByUserId/:userId',async(ctx, next)=>{ 57 router.put('/updateUserByUserId/:userId',async(ctx, next)=>{
34 try{ 58 try{
35 let updateBackData = await userContoller.updateUserByUserId(ctx, next); 59 let updateBackData = await userContoller.updateUserByUserId(ctx, next);
@@ -40,6 +64,9 @@ router.put('/updateUserByUserId/:userId',async(ctx, next)=>{ @@ -40,6 +64,9 @@ router.put('/updateUserByUserId/:userId',async(ctx, next)=>{
40 } 64 }
41 }) 65 })
42 66
  67 +/**
  68 + * 重置密码
  69 + */
43 router.put('/resetPasswordByUserId/:userId',async(ctx, next)=>{ 70 router.put('/resetPasswordByUserId/:userId',async(ctx, next)=>{
44 try{ 71 try{
45 let updateBackData = await userContoller.resetPasswordByUserId(ctx, next); 72 let updateBackData = await userContoller.resetPasswordByUserId(ctx, next);
@@ -50,6 +77,10 @@ router.put('/resetPasswordByUserId/:userId',async(ctx, next)=>{ @@ -50,6 +77,10 @@ router.put('/resetPasswordByUserId/:userId',async(ctx, next)=>{
50 } 77 }
51 }) 78 })
52 79
  80 +
  81 +/**
  82 + * 删除用户
  83 + */
53 router.delete('/delUserByUserId/:userId',async(ctx, next)=>{ 84 router.delete('/delUserByUserId/:userId',async(ctx, next)=>{
54 try{ 85 try{
55 let delData = await userContoller.delUserByUserId(ctx, next); 86 let delData = await userContoller.delUserByUserId(ctx, next);
@@ -60,6 +91,20 @@ router.delete('/delUserByUserId/:userId',async(ctx, next)=>{ @@ -60,6 +91,20 @@ router.delete('/delUserByUserId/:userId',async(ctx, next)=>{
60 } 91 }
61 }) 92 })
62 93
  94 +/**
  95 + * 根据手机号验证码修改密码
  96 + */
  97 +router.put('/updatePwByTelphone',async(ctx, next)=>{
  98 + try{
  99 + let delData = await userContoller.updatePwByTelphone(ctx, next);
  100 + status.successTemp(ctx,200,delData);
  101 + }catch(e){
  102 + console.log(e)
  103 + status.catchError(ctx,400,e.message);
  104 + }
  105 +})
  106 +
  107 +
63 108
64 109
65 110
@@ -13,7 +13,12 @@ function userService(){ @@ -13,7 +13,12 @@ function userService(){
13 userService.prototype.addUser = async(data)=>{ 13 userService.prototype.addUser = async(data)=>{
14 14
15 try{ 15 try{
16 - let oldUser = await userModel.find({where:{loginName:data.loginName}}) 16 + var queryData = [
  17 + {userMobile:data.userMobile},
  18 + {loginName:data.loginName },
  19 + {userEmail:data.userEmail }
  20 + ]
  21 + let oldUser = await userModel.find({where:{$or:queryData}})
17 if(oldUser){ 22 if(oldUser){
18 return {code:400,msg:'用户已存在'} 23 return {code:400,msg:'用户已存在'}
19 } 24 }
@@ -24,9 +29,15 @@ try{ @@ -24,9 +29,15 @@ try{
24 } 29 }
25 } 30 }
26 31
27 -userService.prototype.login = async(name,pw) => { 32 +userService.prototype.login = async(loginName,pw,userEmail) => {
28 try{ 33 try{
29 - let User = await userModel.find({where:{loginName:name}}) 34 + let query;
  35 + if(!loginName){
  36 + query = {userEmail:userEmail};
  37 + }else if(!userEmail){
  38 + query = {loginName:loginName};
  39 + }
  40 + let User = await userModel.find({where:query})
30 console.dir(User) 41 console.dir(User)
31 if(!User){ 42 if(!User){
32 return {code:300,msg:'用户不存在'} 43 return {code:300,msg:'用户不存在'}
@@ -75,6 +86,23 @@ userService.prototype.resetPasswordByUserId = async(userId,pw) => { @@ -75,6 +86,23 @@ userService.prototype.resetPasswordByUserId = async(userId,pw) => {
75 } 86 }
76 } 87 }
77 88
  89 +userService.prototype.updatePwByTelphone = async(tel,pw) => {
  90 + try{
  91 + var t = await sequelize.transaction({ autocommit: true })
  92 + let User = await userModel.findOne({where:{userMobile:tel}})
  93 + if(!User){
  94 + t.rollback
  95 + return {code:0,msg:'用户不存在'}
  96 + }
  97 + let updateUser = await userModel.update({password:pw.md5Pass,salt:pw.salt},{where:{userMobile:tel}});
  98 + t.commit();
  99 + return updateUser
  100 + }catch (err){
  101 + t.rollback();
  102 + throw new Error(err);
  103 + }
  104 +}
  105 +
78 userService.prototype.updateUserByUserId = async(userId,data)=>{ 106 userService.prototype.updateUserByUserId = async(userId,data)=>{
79 try { 107 try {
80 var t = await sequelize.transaction({ autocommit: true }) 108 var t = await sequelize.transaction({ autocommit: true })
  1 +const redis = require('redis');
  2 +const client = redis.createClient(6379, '127.0.0.1');
  3 +
  4 +function createRedis (){
  5 +
  6 +}
  7 +
  8 +createRedis.prototype.setToken = async(id, token) => {
  9 + let ok = await client.set(id, token);
  10 + return ok;
  11 +};
  12 +
  13 +createRedis.prototype.getToken = async(id) => {
  14 + return new Promise((resolve, reject) =>{
  15 + client.get(id,function(err,data){
  16 + if(err){
  17 + reject(err)
  18 + }
  19 + resolve(data)
  20 + });
  21 + })
  22 + };
  23 +
  24 +
  25 + module.exports = new createRedis();