Browse Source

增加solr及redis依赖

Sand 8 years ago
parent
commit
617225da8a
100 changed files with 12082 additions and 6217 deletions
  1. 0 227
      src/doctor/node_modules/enum/README.md
  2. 0 337
      src/doctor/node_modules/enum/dist/enum.js
  3. 0 104
      src/doctor/node_modules/enum/dist/enumItem.js
  4. 0 11
      src/doctor/node_modules/enum/dist/indexOf.js
  5. 0 15
      src/doctor/node_modules/enum/dist/isType.js
  6. 0 1933
      src/doctor/node_modules/enum/enum-2.3.0.js
  7. 0 1
      src/doctor/node_modules/enum/enum-2.3.0.min.js
  8. 0 1
      src/doctor/node_modules/enum/index.js
  9. 0 19
      src/doctor/node_modules/enum/licence
  10. 0 100
      src/doctor/node_modules/enum/package.json
  11. 4 0
      src/doctor/node_modules/redis/.eslintignore
  12. 108 0
      src/doctor/node_modules/redis/.eslintrc
  13. 15 0
      src/doctor/node_modules/redis/.github/ISSUE_TEMPLATE.md
  14. 14 0
      src/doctor/node_modules/redis/.github/PULL_REQUEST_TEMPLATE.md
  15. 9 7
      src/doctor/node_modules/redis/.npmignore
  16. 24 0
      src/doctor/node_modules/redis/LICENSE
  17. 523 427
      src/doctor/node_modules/redis/README.md
  18. 803 0
      src/doctor/node_modules/redis/changelog.md
  19. 0 80
      src/doctor/node_modules/redis/connection_breaker.js
  20. 864 1055
      src/doctor/node_modules/redis/index.js
  21. 0 1284
      src/doctor/node_modules/redis/index.js.bak
  22. 16 0
      src/doctor/node_modules/redis/lib/command.js
  23. 113 163
      src/doctor/node_modules/redis/lib/commands.js
  24. 79 0
      src/doctor/node_modules/redis/lib/createClient.js
  25. 46 0
      src/doctor/node_modules/redis/lib/customErrors.js
  26. 11 0
      src/doctor/node_modules/redis/lib/debug.js
  27. 112 0
      src/doctor/node_modules/redis/lib/extendedApi.js
  28. 617 0
      src/doctor/node_modules/redis/lib/individualCommands.js
  29. 187 0
      src/doctor/node_modules/redis/lib/multi.js
  30. 0 46
      src/doctor/node_modules/redis/lib/parser/hiredis.js
  31. 0 301
      src/doctor/node_modules/redis/lib/parser/javascript.js
  32. 0 59
      src/doctor/node_modules/redis/lib/queue.js
  33. 0 12
      src/doctor/node_modules/redis/lib/to_array.js
  34. 0 11
      src/doctor/node_modules/redis/lib/util.js
  35. 134 0
      src/doctor/node_modules/redis/lib/utils.js
  36. 31 0
      src/doctor/node_modules/redis/node_modules/double-ended-queue/.npmignore
  37. 188 0
      src/doctor/node_modules/redis/node_modules/double-ended-queue/Gruntfile.js
  38. 19 0
      src/doctor/node_modules/redis/node_modules/double-ended-queue/LICENSE
  39. 293 0
      src/doctor/node_modules/redis/node_modules/double-ended-queue/README.md
  40. 275 0
      src/doctor/node_modules/redis/node_modules/double-ended-queue/js/deque.js
  41. 69 0
      src/doctor/node_modules/redis/node_modules/double-ended-queue/package.json
  42. 29 0
      src/doctor/node_modules/redis/node_modules/redis-commands/.npmignore
  43. 9 0
      src/doctor/node_modules/redis/node_modules/redis-commands/.travis.yml
  44. 22 0
      src/doctor/node_modules/redis/node_modules/redis-commands/LICENSE
  45. 51 0
      src/doctor/node_modules/redis/node_modules/redis-commands/README.md
  46. 26 0
      src/doctor/node_modules/redis/node_modules/redis-commands/changelog.md
  47. 1796 0
      src/doctor/node_modules/redis/node_modules/redis-commands/commands.json
  48. 155 0
      src/doctor/node_modules/redis/node_modules/redis-commands/index.js
  49. 78 0
      src/doctor/node_modules/redis/node_modules/redis-commands/package.json
  50. 120 0
      src/doctor/node_modules/redis/node_modules/redis-commands/test/index.js
  51. 62 0
      src/doctor/node_modules/redis/node_modules/redis-commands/tools/build.js
  52. 6 0
      src/doctor/node_modules/redis/node_modules/redis-parser/.codeclimate.yml
  53. 12 0
      src/doctor/node_modules/redis/node_modules/redis-parser/.npmignore
  54. 22 0
      src/doctor/node_modules/redis/node_modules/redis-parser/LICENSE
  55. 149 0
      src/doctor/node_modules/redis/node_modules/redis-parser/README.md
  56. 101 0
      src/doctor/node_modules/redis/node_modules/redis-parser/changelog.md
  57. 4 0
      src/doctor/node_modules/redis/node_modules/redis-parser/index.js
  58. 1 0
      src/doctor/node_modules/redis/node_modules/redis-parser/isolate-0x28e7b50-v8.log
  59. 1 0
      src/doctor/node_modules/redis/node_modules/redis-parser/isolate-0x3615b50-v8.log
  60. 52 0
      src/doctor/node_modules/redis/node_modules/redis-parser/lib/hiredis.js
  61. 498 0
      src/doctor/node_modules/redis/node_modules/redis-parser/lib/parser.js
  62. 24 0
      src/doctor/node_modules/redis/node_modules/redis-parser/lib/replyError.js
  63. 81 0
      src/doctor/node_modules/redis/node_modules/redis-parser/package.json
  64. 69 24
      src/doctor/node_modules/redis/package.json
  65. 1 0
      src/doctor/node_modules/solr-client/.idea/.name
  66. 11 0
      src/doctor/node_modules/solr-client/.idea/inspectionProfiles/Project_Default.xml
  67. 7 0
      src/doctor/node_modules/solr-client/.idea/inspectionProfiles/profiles_settings.xml
  68. 6 0
      src/doctor/node_modules/solr-client/.idea/jsLibraryMappings.xml
  69. 14 0
      src/doctor/node_modules/solr-client/.idea/libraries/solr_node_client_node_modules.xml
  70. 13 0
      src/doctor/node_modules/solr-client/.idea/misc.xml
  71. 8 0
      src/doctor/node_modules/solr-client/.idea/modules.xml
  72. 9 0
      src/doctor/node_modules/solr-client/.idea/solr-node-client.iml
  73. 6 0
      src/doctor/node_modules/solr-client/.idea/vcs.xml
  74. 770 0
      src/doctor/node_modules/solr-client/.idea/workspace.xml
  75. 8 0
      src/doctor/node_modules/solr-client/.npmignore
  76. 7 0
      src/doctor/node_modules/solr-client/.travis.yml
  77. 17 0
      src/doctor/node_modules/solr-client/CONTRIBUTIONS.md
  78. 22 0
      src/doctor/node_modules/solr-client/LICENCE
  79. 213 0
      src/doctor/node_modules/solr-client/README.md
  80. 644 0
      src/doctor/node_modules/solr-client/lib/collection.js
  81. 49 0
      src/doctor/node_modules/solr-client/lib/error/solr-error.js
  82. 861 0
      src/doctor/node_modules/solr-client/lib/query.js
  83. 957 0
      src/doctor/node_modules/solr-client/lib/solr.js
  84. 15 0
      src/doctor/node_modules/solr-client/lib/utils/array.js
  85. 136 0
      src/doctor/node_modules/solr-client/lib/utils/format.js
  86. 21 0
      src/doctor/node_modules/solr-client/lib/utils/type.js
  87. 37 0
      src/doctor/node_modules/solr-client/lib/utils/version.js
  88. 10 0
      src/doctor/node_modules/solr-client/main.js
  89. 15 0
      src/doctor/node_modules/solr-client/node_modules/.bin/JSONStream
  90. 7 0
      src/doctor/node_modules/solr-client/node_modules/.bin/JSONStream.cmd
  91. 2 0
      src/doctor/node_modules/solr-client/node_modules/JSONStream/.npmignore
  92. 3 0
      src/doctor/node_modules/solr-client/node_modules/JSONStream/.travis.yml
  93. 15 0
      src/doctor/node_modules/solr-client/node_modules/JSONStream/LICENSE.APACHE2
  94. 24 0
      src/doctor/node_modules/solr-client/node_modules/JSONStream/LICENSE.MIT
  95. 13 0
      src/doctor/node_modules/solr-client/node_modules/JSONStream/examples/all_docs.js
  96. 203 0
      src/doctor/node_modules/solr-client/node_modules/JSONStream/index.js
  97. 1 0
      src/doctor/node_modules/solr-client/node_modules/JSONStream/node_modules/jsonparse/.npmignore
  98. 24 0
      src/doctor/node_modules/solr-client/node_modules/JSONStream/node_modules/jsonparse/LICENSE
  99. 11 0
      src/doctor/node_modules/solr-client/node_modules/JSONStream/node_modules/jsonparse/README.markdown
  100. 0 0
      src/doctor/node_modules/solr-client/node_modules/JSONStream/node_modules/jsonparse/bench.js

+ 0 - 227
src/doctor/node_modules/enum/README.md

@ -1,227 +0,0 @@
# Introduction
[![travis](https://img.shields.io/travis/adrai/enum.svg)](https://travis-ci.org/adrai/enum) [![npm](https://img.shields.io/npm/v/enum.svg)](https://npmjs.org/package/enum) [![Sauce Test Status](https://saucelabs.com/buildstatus/adrirai)](https://saucelabs.com/u/adrirai)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/adrirai.svg)](https://saucelabs.com/u/adrirai)
<!---
[![browser support](https://ci.testling.com/adrai/enum.png)](https://ci.testling.com/adrai/enum)
-->
Enum is a javascript module that introduces the Enum Type. It works for node.js and in the browser.
...and [ref](https://github.com/TooTallNate/ref) compatible [Known Types](https://github.com/TooTallNate/ref/wiki/Known-%22types%22)
# Dependencies
No dependencies!
# Download
Releases for a browser are available for download from GitHub.
| **Version** | **Description** | **Size** |
|:------------|:----------------|:---------|
| `enum-2.2.0.js` | *uncompressed, with comments* | [Download](https://raw.github.com/adrai/enum/master/enum-2.2.0.js) |
| `enum-2.2.0.min.js` | *compressed, without comments* | [Download](https://raw.github.com/adrai/enum/master/enum-2.2.0.min.js) |
# Installation (node.js)
    $ npm install enum
# Installation (browser)
    <script src="enum.js"></script>
# Usage
````js
// use it as module
var Enum = require('enum');
// or extend node.js with this new type
require('enum').register();
// define a simple enum (automatically flaggable -> A: 0x01, B: 0x02, C: 0x04)
//Uses bitwise 'OR' operation in between the values and creates enumerated constants. For example, if 'Read':1, 'Write':2, then ReadWrite= Read | Write = 1 | 2 = 3;
var myEnum = new Enum(['A', 'B', 'C']);
//define a flagged enum object to create a multicolor option; just pass an array
var myEnum = new Enum(['Black', 'Red', 'Green', 'Blue']);
myEnum; //=> Enum {_options: Object, enums: Array[4], Black: EnumItem, Red: EnumItem, Green: EnumItem….....}
myEnum.isFlaggable; //=> true
for(var i=1; i<8; i++){ console.log(myEnum.get(i).value + '=> '+ myEnum.get(i).key)}
    1=> Black
    2=> Red
    3=> Black | Red
    4=> Green
    5=> Black | Green
    6=> Red | Green
    7=> Black | Red | Green
// define an enum with own values
var myEnum = new Enum({'A': 1, 'B': 2, 'C': 4});
// if defining a flaggable enum, you can define your own separator (default is ' | ')
var myEnum = new Enum(['A', 'B', 'C'], { separator: ' | ' });
// if you want your enum to have a name define it in the options
var myEnum = new Enum(['A', 'B', 'C'], { name: 'MyEnum' });
// or
var myEnum = new Enum(['A', 'B', 'C'], 'MyEnum');
// if you want your enum to have an explicit "endianness", define it in the options
// (defaults to `os.endianness()`)
var myEnum = new Enum(['A', 'B', 'C'], { endianness: 'BE' });
// if you want your enum to be not case sensitive
// (defaults to `false`)
var myEnum = new Enum(['One', 'tWo', 'ThrEE'], { ignoreCase: true });
myEnum.get('one'); // => myEnum.One
myEnum.get('TWO'); // => myEnum.tWo
myEnum.ThrEE.is('three'); // => true
// this option will make instances of Enum non-extensible
// (defaults to `false`)
var myEnum = new Enum(['ONE', 'TWO', 'THREE'], { freez: true });
//define enum type without flag
var myEnum = new Enum({'None': 0, 'Black':1, 'Red': 2, 'Red2': 3, 'Green': 4, 'Blue': 5});
myEnum; //=>  Enum {_options: Object, enums: Array[6], None: EnumItem, Black: EnumItem, Red: EnumItem…........}
myEnum.isFlaggable; //=> false
myEnum.toJSON(); // returns {'None': 0, 'Black':1, 'Red': 2, 'Red2': 3, 'Green': 4, 'Blue': 5}
JSON.stringify(myEnum); // returns '{"None":0,"Black":1,"Red":2,"Red2":3,"Green":4,"Blue":5}'
for(var i=0; i<=5; i++){ console.log(myEnum.get(i).value + '=> '+ myEnum.get(i).key)}
    0=> None
    1=> Black
    2=> Red
    3=> Red2
    4=> Green
    5=> Blue
// iterating over an enum
myEnum.enums.forEach(function(enumItem) {
  console.log(enumItem.key);
});
// => None
// => Black
// => Red
// => Red2
// => Green
// => Blue
// get your item
myEnum.A
// or
myEnum.get('A')
// or
myEnum.get(1)
// or
myEnum.get('A | B')
// or
myEnum.get(3)
// get your value
myEnum.A.value
// get your key
myEnum.A.key
// get all items
myEnum.enums // returns all enums in an array
// compare
myEnum.A.is(myEnum.A)
// or
myEnum.A.is('A')
// or
myEnum.A.is(1)
// or
myEnum.A == 'A'
// or
myEnum.A == myEnum.A
// or
myEnum.A === myEnum.A
// check flag
var myItem = myEnum.get(3); // or [myEnum.get('A | B')]
myItem.has(myEnum.A)
// or
myItem.has('A')
// or
myItem.has(1)
// other functions
myItem.toString() // returns 'A | C'
myItem.toJSON() // returns '"A | C"'
myItem.valueOf() // returns 3
JSON.stringify(myItem) // returns '"A | C"'
//Type Safety:
//Newly created enumerable objects are Type-Safe in a way that it's non-configurable and no longer extensible.
//Each EnumItem has a beack-reference to a constructor and they are implicitly final.
Object.getOwnPropertyDescriptor(myEnum, 'Red'); //=> Object {value: EnumItem, writable: false, enumerable: true, configurable: false}
Object.isExtensible(myEnum); //=> false
myEnum instanceof Enum; //=> true
//Instances of Enum created with 'new' from similar objects are not equal
myEnum1=new Enum({'A':1, 'B':2, 'C':4});
myEnum2=new Enum({'A':1, 'B':2, 'C':4});
myEnum1 == myEnum2 //=> false
myEnum1.A == myEnum2.A //=> false
myEnum1.A.value == myEnum2.A.value //=> true
//This enum object has no properties other than those defined during its creation. Existing Data is 'Persistent' and preserves the original version
myEnum.B.value; //=> 2
myEnum.B = 5; //=> Throws TypeError
delete myEnum.B; //=> false
myEnum.D = 6; //=> doesn't add to the enum object, silently ignores
myEnum.D; // undefined
//Try to define new property throws TypeError
Object.defineProperty(myEnum, D, {value:6, writable:false, enumerable:true});
//=>TypeError: Cannot define property:D, object is not extensible.
````
# License
Copyright (c) 2015 Adriano Raiano, Christoph Hermann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

+ 0 - 337
src/doctor/node_modules/enum/dist/enum.js

@ -1,337 +0,0 @@
"use strict";
var _interopRequire = function (obj) { return obj && obj.__esModule ? obj["default"] : obj; };
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
var os = _interopRequire(require("os"));
var EnumItem = _interopRequire(require("./enumItem"));
var isString = require("./isType").isString;
var indexOf = require("./indexOf").indexOf;
var endianness = os.endianness();
/**
 * Represents an Enum with enum items.
 * @param {Array || Object}  map     This are the enum items.
 * @param {String || Object} options This are options. [optional]
 */
var Enum = (function () {
  function Enum(map, options) {
    var _this = this;
    _classCallCheck(this, Enum);
    /* implement the "ref type interface", so that Enum types can
     * be used in `node-ffi` function declarations and invokations.
     * In C, these Enums act as `uint32_t` types.
     *
     * https://github.com/TooTallNate/ref#the-type-interface
     */
    this.size = 4;
    this.indirection = 1;
    if (options && isString(options)) {
      options = { name: options };
    }
    this._options = options || {};
    this._options.separator = this._options.separator || " | ";
    this._options.endianness = this._options.endianness || endianness;
    this._options.ignoreCase = this._options.ignoreCase || false;
    this._options.freez = this._options.freez || false;
    this.enums = [];
    if (map.length) {
      this._enumLastIndex = map.length;
      var array = map;
      map = {};
      for (var i = 0; i < array.length; i++) {
        map[array[i]] = Math.pow(2, i);
      }
    }
    for (var member in map) {
      guardReservedKeys(this._options.name, member);
      this[member] = new EnumItem(member, map[member], { ignoreCase: this._options.ignoreCase });
      this.enums.push(this[member]);
    }
    this._enumMap = map;
    if (this._options.ignoreCase) {
      this.getLowerCaseEnums = function () {
        var res = {};
        for (var i = 0, len = this.enums.length; i < len; i++) {
          res[this.enums[i].key.toLowerCase()] = this.enums[i];
        }
        return res;
      };
    }
    if (this._options.name) {
      this.name = this._options.name;
    }
    var isFlaggable = function () {
      for (var i = 0, len = _this.enums.length; i < len; i++) {
        var e = _this.enums[i];
        if (!(e.value !== 0 && !(e.value & e.value - 1))) {
          return false;
        }
      }
      return true;
    };
    this.isFlaggable = isFlaggable();
    if (this._options.freez) {
      this.freezeEnums(); //this will make instances of Enum non-extensible
    }
  }
  /**
   * Returns the appropriate EnumItem key.
   * @param  {EnumItem || String || Number} key The object to get with.
   * @return {String}                           The get result.
   */
  Enum.prototype.getKey = function getKey(value) {
    var item = this.get(value);
    if (item) {
      return item.key;
    }
  };
  /**
   * Returns the appropriate EnumItem value.
   * @param  {EnumItem || String || Number} key The object to get with.
   * @return {Number}                           The get result.
   */
  Enum.prototype.getValue = function getValue(key) {
    var item = this.get(key);
    if (item) {
      return item.value;
    }
  };
  /**
   * Returns the appropriate EnumItem.
   * @param  {EnumItem || String || Number} key The object to get with.
   * @return {EnumItem}                         The get result.
   */
  Enum.prototype.get = function get(key, offset) {
    if (key === null || key === undefined) {
      return;
    } // Buffer instance support, part of the ref Type interface
    if (Buffer.isBuffer(key)) {
      key = key["readUInt32" + this._options.endianness](offset || 0);
    }
    if (EnumItem.isEnumItem(key)) {
      var foundIndex = indexOf.call(this.enums, key);
      if (foundIndex >= 0) {
        return key;
      }
      if (!this.isFlaggable || this.isFlaggable && key.key.indexOf(this._options.separator) < 0) {
        return;
      }
      return this.get(key.key);
    } else if (isString(key)) {
      var enums = this;
      if (this._options.ignoreCase) {
        enums = this.getLowerCaseEnums();
        key = key.toLowerCase();
      }
      if (key.indexOf(this._options.separator) > 0) {
        var parts = key.split(this._options.separator);
        var value = 0;
        for (var i = 0; i < parts.length; i++) {
          var part = parts[i];
          value |= enums[part].value;
        }
        return new EnumItem(key, value);
      } else {
        return enums[key];
      }
    } else {
      for (var m in this) {
        if (this.hasOwnProperty(m)) {
          if (this[m].value === key) {
            return this[m];
          }
        }
      }
      var result = null;
      if (this.isFlaggable) {
        for (var n in this) {
          if (this.hasOwnProperty(n)) {
            if ((key & this[n].value) !== 0) {
              if (result) {
                result += this._options.separator;
              } else {
                result = "";
              }
              result += n;
            }
          }
        }
      }
      return this.get(result || null);
    }
  };
  /**
   * Sets the Enum "value" onto the give `buffer` at the specified `offset`.
   * Part of the ref "Type interface".
   *
   * @param  {Buffer} buffer The Buffer instance to write to.
   * @param  {Number} offset The offset in the buffer to write to. Default 0.
   * @param  {EnumItem || String || Number} value The EnumItem to write.
   */
  Enum.prototype.set = function set(buffer, offset, value) {
    var item = this.get(value);
    if (item) {
      return buffer["writeUInt32" + this._options.endianness](item.value, offset || 0);
    }
  };
  /**
   * Define freezeEnums() as a property of the prototype.
   * make enumerable items nonconfigurable and deep freeze the properties. Throw Error on property setter.
   */
  Enum.prototype.freezeEnums = function freezeEnums() {
    function envSupportsFreezing() {
      return Object.isFrozen && Object.isSealed && Object.getOwnPropertyNames && Object.getOwnPropertyDescriptor && Object.defineProperties && Object.__defineGetter__ && Object.__defineSetter__;
    }
    function freezer(o) {
      var props = Object.getOwnPropertyNames(o);
      props.forEach(function (p) {
        if (!Object.getOwnPropertyDescriptor(o, p).configurable) {
          return;
        }
        Object.defineProperties(o, p, { writable: false, configurable: false });
      });
      return o;
    }
    function getPropertyValue(value) {
      return value;
    }
    function deepFreezeEnums(o) {
      if (typeof o !== "object" || o === null || Object.isFrozen(o) || Object.isSealed(o)) {
        return;
      }
      for (var key in o) {
        if (o.hasOwnProperty(key)) {
          o.__defineGetter__(key, getPropertyValue.bind(null, o[key]));
          o.__defineSetter__(key, function throwPropertySetError(value) {
            throw TypeError("Cannot redefine property; Enum Type is not extensible.");
          });
          deepFreezeEnums(o[key]);
        }
      }
      if (Object.freeze) {
        Object.freeze(o);
      } else {
        freezer(o);
      }
    }
    if (envSupportsFreezing()) {
      deepFreezeEnums(this);
    }
    return this;
  };
  /**
   * Returns JSON object representation of this Enum.
   * @return {String} JSON object representation of this Enum.
   */
  Enum.prototype.toJSON = function toJSON() {
    return this._enumMap;
  };
  /**
   * Extends the existing Enum with a New Map.
   * @param  {Array}  map  Map to extend from
   */
  Enum.prototype.extend = function extend(map) {
    if (map.length) {
      var array = map;
      map = {};
      for (var i = 0; i < array.length; i++) {
        var exponent = this._enumLastIndex + i;
        map[array[i]] = Math.pow(2, exponent);
      }
      for (var member in map) {
        guardReservedKeys(this._options.name, member);
        this[member] = new EnumItem(member, map[member], { ignoreCase: this._options.ignoreCase });
        this.enums.push(this[member]);
      }
      for (var key in this._enumMap) {
        map[key] = this._enumMap[key];
      }
      this._enumLastIndex += map.length;
      this._enumMap = map;
      if (this._options.freez) {
        this.freezeEnums(); //this will make instances of new Enum non-extensible
      }
    }
  };
  /**
   * Registers the Enum Type globally in node.js.
   * @param  {String} key Global variable. [optional]
   */
  Enum.register = function register() {
    var key = arguments[0] === undefined ? "Enum" : arguments[0];
    if (!global[key]) {
      global[key] = Enum;
    }
  };
  return Enum;
})();
module.exports = Enum;
// private
var reservedKeys = ["_options", "get", "getKey", "getValue", "enums", "isFlaggable", "_enumMap", "toJSON", "_enumLastIndex"];
function guardReservedKeys(customName, key) {
  if (customName && key === "name" || indexOf.call(reservedKeys, key) >= 0) {
    throw new Error("Enum key " + key + " is a reserved word!");
  }
}

+ 0 - 104
src/doctor/node_modules/enum/dist/enumItem.js

@ -1,104 +0,0 @@
"use strict";
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
var _isType = require("./isType");
var isObject = _isType.isObject;
var isString = _isType.isString;
/**
 * Represents an Item of an Enum.
 * @param {String} key   The Enum key.
 * @param {Number} value The Enum value.
 */
var EnumItem = (function () {
  /*constructor reference so that, this.constructor===EnumItem//=>true */
  function EnumItem(key, value) {
    var options = arguments[2] === undefined ? {} : arguments[2];
    _classCallCheck(this, EnumItem);
    this.key = key;
    this.value = value;
    this._options = options;
    this._options.ignoreCase = this._options.ignoreCase || false;
  }
  /**
   * Checks if the flagged EnumItem has the passing object.
   * @param  {EnumItem || String || Number} value The object to check with.
   * @return {Boolean}                            The check result.
   */
  EnumItem.prototype.has = function has(value) {
    if (EnumItem.isEnumItem(value)) {
      return (this.value & value.value) !== 0;
    } else if (isString(value)) {
      if (this._options.ignoreCase) {
        return this.key.toLowerCase().indexOf(value.toLowerCase()) >= 0;
      }
      return this.key.indexOf(value) >= 0;
    } else {
      return (this.value & value) !== 0;
    }
  };
  /**
   * Checks if the EnumItem is the same as the passing object.
   * @param  {EnumItem || String || Number} key The object to check with.
   * @return {Boolean}                          The check result.
   */
  EnumItem.prototype.is = function is(key) {
    if (EnumItem.isEnumItem(key)) {
      return this.key === key.key;
    } else if (isString(key)) {
      if (this._options.ignoreCase) {
        return this.key.toLowerCase() === key.toLowerCase();
      }
      return this.key === key;
    } else {
      return this.value === key;
    }
  };
  /**
   * Returns String representation of this EnumItem.
   * @return {String} String representation of this EnumItem.
   */
  EnumItem.prototype.toString = function toString() {
    return this.key;
  };
  /**
   * Returns JSON object representation of this EnumItem.
   * @return {String} JSON object representation of this EnumItem.
   */
  EnumItem.prototype.toJSON = function toJSON() {
    return this.key;
  };
  /**
   * Returns the value to compare with.
   * @return {String} The value to compare with.
   */
  EnumItem.prototype.valueOf = function valueOf() {
    return this.value;
  };
  EnumItem.isEnumItem = function isEnumItem(value) {
    return value instanceof EnumItem || isObject(value) && value.key !== undefined && value.value !== undefined;
  };
  return EnumItem;
})();
module.exports = EnumItem;

+ 0 - 11
src/doctor/node_modules/enum/dist/indexOf.js

@ -1,11 +0,0 @@
"use strict";
exports.__esModule = true;
var indexOf = Array.prototype.indexOf || function (find, i /*opt*/) {
  if (i === undefined) i = 0;
  if (i < 0) i += this.length;
  if (i < 0) i = 0;
  for (var n = this.length; i < n; i++) if (i in this && this[i] === find) return i;
  return -1;
};
exports.indexOf = indexOf;

+ 0 - 15
src/doctor/node_modules/enum/dist/isType.js

@ -1,15 +0,0 @@
"use strict";
exports.__esModule = true;
var isType = function (type, value) {
  return typeof value === type;
};
exports.isType = isType;
var isObject = function (value) {
  return isType("object", value);
};
exports.isObject = isObject;
var isString = function (value) {
  return isType("string", value);
};
exports.isString = isString;

File diff suppressed because it is too large
+ 0 - 1933
src/doctor/node_modules/enum/enum-2.3.0.js


File diff suppressed because it is too large
+ 0 - 1
src/doctor/node_modules/enum/enum-2.3.0.min.js


+ 0 - 1
src/doctor/node_modules/enum/index.js

@ -1 +0,0 @@
module.exports = require('./dist/enum');

+ 0 - 19
src/doctor/node_modules/enum/licence

@ -1,19 +0,0 @@
Copyright (c) 2015 Adriano Raiano, Christoph Hermann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

+ 0 - 100
src/doctor/node_modules/enum/package.json

@ -1,100 +0,0 @@
{
  "author": {
    "name": "adrai"
  },
  "name": "enum",
  "version": "2.3.0",
  "private": false,
  "main": "index.js",
  "engines": {
    "node": ">=0.6.0"
  },
  "directories": {
    "lib": "./lib"
  },
  "dependencies": {},
  "devDependencies": {
    "expect.js": ">= 0.1.2",
    "gulp": "^3.8.11",
    "gulp-babel": "^4.0.0",
    "gulp-bench": "^1.1.0",
    "gulp-browserify": "^0.5.1",
    "gulp-mocha": "^2.0.0",
    "gulp-rename": "^1.2.0",
    "gulp-rimraf": "^0.1.1",
    "gulp-shell": "^0.4.0",
    "gulp-uglify": "^1.1.0",
    "mocha": ">= 1.0.1",
    "zuul": ">= 1.0.1"
  },
  "description": "Enum is a javascript module that introduces the Enum Type. It works for node.js and in the browser.",
  "keywords": [
    "enum"
  ],
  "homepage": "https://github.com/adrai/enum",
  "repository": {
    "type": "git",
    "url": "git+ssh://git@github.com/adrai/enum.git"
  },
  "bugs": {
    "url": "https://github.com/adrai/enum/issues"
  },
  "licenses": [
    {
      "type": "MIT",
      "url": "https://raw.github.com/adrai/enum/master/licence"
    }
  ],
  "scripts": {
    "test": "gulp test-ci",
    "build": "gulp build",
    "prepublish": "gulp"
  },
  "testling": {
    "browsers": [
      "ie/6..latest",
      "firefox/17..latest",
      "firefox/nightly",
      "chrome/22..latest",
      "chrome/canary",
      "opera/12..latest",
      "opera/next",
      "safari/5.1..latest",
      "ipad/6.0..latest",
      "iphone/6.0..latest",
      "android-browser/4.2..latest"
    ],
    "harness": "mocha",
    "files": "test/*.js"
  },
  "gitHead": "9b6ee788287b50d4d296527c10dcf7634ad3f7e8",
  "_id": "enum@2.3.0",
  "_shasum": "02ab401a76e738da3240769eec0873fcf9f3cdba",
  "_from": "enum@>=2.3.0 <2.4.0",
  "_npmVersion": "2.14.7",
  "_nodeVersion": "4.2.3",
  "_npmUser": {
    "name": "adrai",
    "email": "adriano@raiano.ch"
  },
  "maintainers": [
    {
      "name": "adrai",
      "email": "adriano@raiano.ch"
    },
    {
      "name": "schtoeffel",
      "email": "schtoeffel@gmail.com"
    }
  ],
  "dist": {
    "shasum": "02ab401a76e738da3240769eec0873fcf9f3cdba",
    "size": 48967,
    "noattachment": false,
    "tarball": "http://registry.npm.taobao.org/enum/download/enum-2.3.0.tgz"
  },
  "publish_time": 1449649476497,
  "_cnpm_publish_time": 1449649476497,
  "_resolved": "https://registry.npm.taobao.org/enum/download/enum-2.3.0.tgz",
  "readme": "ERROR: No README data found!"
}

+ 4 - 0
src/doctor/node_modules/redis/.eslintignore

@ -0,0 +1,4 @@
node_modules/**
coverage/**
**.md
**.log

+ 108 - 0
src/doctor/node_modules/redis/.eslintrc

@ -0,0 +1,108 @@
env:
  node: true
  es6: false
rules:
  # Possible Errors
  # http://eslint.org/docs/rules/#possible-errors
  comma-dangle: [2, "only-multiline"]
  no-constant-condition: 2
  no-control-regex: 2
  no-debugger: 2
  no-dupe-args: 2
  no-dupe-keys: 2
  no-duplicate-case: 2
  no-empty: 2
  no-empty-character-class: 2
  no-ex-assign: 2
  no-extra-boolean-cast : 2
  no-extra-parens: [2, "functions"]
  no-extra-semi: 2
  no-func-assign: 2
  no-invalid-regexp: 2
  no-irregular-whitespace: 2
  no-negated-in-lhs: 2
  no-obj-calls: 2
  no-regex-spaces: 2
  no-sparse-arrays: 2
  no-inner-declarations: 2
  no-unexpected-multiline: 2
  no-unreachable: 2
  use-isnan: 2
  valid-typeof: 2
  # Best Practices
  # http://eslint.org/docs/rules/#best-practices
  array-callback-return: 2
  block-scoped-var: 2
  dot-notation: 2
  eqeqeq: 2
  no-else-return: 2
  no-extend-native: 2
  no-floating-decimal: 2
  no-extra-bind: 2
  no-fallthrough: 2
  no-labels: 2
  no-lone-blocks: 2
  no-loop-func: 2
  no-multi-spaces: 2
  no-multi-str: 2
  no-native-reassign: 2
  no-new-wrappers: 2
  no-octal: 2
  no-proto: 2
  no-redeclare: 2
  no-return-assign: 2
  no-self-assign: 2
  no-self-compare: 2
  no-sequences: 2
  no-throw-literal: 2
  no-useless-call: 2
  no-useless-concat: 2
  no-useless-escape: 2
  no-void: 2
  no-unmodified-loop-condition: 2
  yoda: 2
  # Strict Mode
  # http://eslint.org/docs/rules/#strict-mode
  strict: [2, "global"]
  # Variables
  # http://eslint.org/docs/rules/#variables
  no-delete-var: 2
  no-shadow-restricted-names: 2
  no-undef: 2
  no-unused-vars: [2, {"args": "none"}]
  # http://eslint.org/docs/rules/#nodejs-and-commonjs
  no-mixed-requires: 2
  no-new-require: 2
  no-path-concat: 2
  # Stylistic Issues
  # http://eslint.org/docs/rules/#stylistic-issues
  comma-spacing: 2
  eol-last: 2
  indent: [2, 4, {SwitchCase: 2}]
  keyword-spacing: 2
  max-len: [2, 200, 2]
  new-parens: 2
  no-mixed-spaces-and-tabs: 2
  no-multiple-empty-lines: [2, {max: 2}]
  no-trailing-spaces: 2
  quotes: [2, "single", "avoid-escape"]
  semi: 2
  space-before-blocks: [2, "always"]
  space-before-function-paren: [2, "always"]
  space-in-parens: [2, "never"]
  space-infix-ops: 2
  space-unary-ops: 2
globals:
  it: true
  describe: true
  before: true
  after: true
  beforeEach: true
  afterEach: true

+ 15 - 0
src/doctor/node_modules/redis/.github/ISSUE_TEMPLATE.md

@ -0,0 +1,15 @@
_Thanks for wanting to report an issue you've found in node_redis. Please delete
this text and fill in the template below. Please note that the issue tracker is only
for bug reports or feature requests. If you have a question, please ask that on [gitter].
If unsure about something, just do as best as you're able._
_Note that it will be much easier to fix the issue if a test case that reproduces
the problem is provided. It is of course not always possible to reduce your code
to a small test case, but it's highly appreciated to have as much data as possible.
Thank you!_
* **Version**: What node_redis and what redis version is the issue happening on?
* **Platform**: What platform / version? (For example Node.js 0.10 or Node.js 5.7.0 on Windows 7 / Ubuntu 15.10 / Azure)
* **Description**: Description of your issue, stack traces from errors and code that reproduces the issue
[gitter]: https://gitter.im/NodeRedis/node_redis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge

+ 14 - 0
src/doctor/node_modules/redis/.github/PULL_REQUEST_TEMPLATE.md

@ -0,0 +1,14 @@
### Pull Request check-list
_Please make sure to review and check all of these items:_
- [ ] Does `npm test` pass with this change (including linting)?
- [ ] Is the new or changed code fully tested?
- [ ] Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?
_NOTE: these things are not required to open a PR and can be done
afterwards / while the PR is open._
### Description of change
_Please provide a description of the change here._

+ 9 - 7
src/doctor/node_modules/redis/.npmignore

@ -1,8 +1,10 @@
examples/
benches/
test.js
diff_multi_bench_output.js
generate_commands.js
multi_bench.js
test-unref.js
changelog.md
benchmarks/
test/
.nyc_output/
coverage/
.tern-port
*.log
*.rdb
*.out
*.yml

+ 24 - 0
src/doctor/node_modules/redis/LICENSE

@ -0,0 +1,24 @@
LICENSE - "MIT License"
Copyright (c) 2016 by NodeRedis
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large
+ 523 - 427
src/doctor/node_modules/redis/README.md


+ 803 - 0
src/doctor/node_modules/redis/changelog.md

@ -0,0 +1,803 @@
Changelog
=========
## v.2.6.3 - 31 Oct, 2016
Bugfixes
-  Do not change the tls setting to camel_case
-  Fix domain handling in combination with the offline queue (2.5.3 regression)
## v.2.6.2 - 16 Jun, 2016
Bugfixes
-  Fixed individual callbacks of a transaction not being called (2.6.0 regression)
## v.2.6.1 - 02 Jun, 2016
Bugfixes
-  Fixed invalid function name being exported
## v.2.6.0 - 01 Jun, 2016
In addition to the pre-releases the following changes exist in v.2.6.0:
Features
-  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))
 -  The JS parser is from now on the new default as it is a lot faster than the hiredis parser
 -  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.
-  Added name property to all Redis functions (Node.js >= 4.0)
-  Improved stack traces in development and debug mode
Bugfixes
-  Reverted support for `__proto__` (v.2.6.0-2) to prevent and breaking change
Deprecations
-  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
## v.2.6.0-2 - 29 Apr, 2016
Features
-  Added support for the new [CLIENT REPLY ON|OFF|SKIP](http://redis.io/commands/client-reply) command (Redis v.3.2)
-  Added support for camelCase
 -  The Node.js landscape default is to use camelCase. node_redis is a bit out of the box here
    but from now on it is possible to use both, just as you prefer!
 -  If there's any documented variable missing as camelCased, please open a issue for it
-  Improve error handling significantly
 -  Only emit an error if the error has not already been handled in a callback
 -  Improved unspecific error messages e.g. "Connection gone from end / close event"
 -  Added `args` to command errors to improve identification of the error
 -  Added origin to errors if there's e.g. a connection error
 -  Added ReplyError class. All Redis errors are from now on going to be of that class
 -  Added AbortError class. A subclass of AbortError. All unresolved and by node_redis rejected commands are from now on of that class
 -  Added AggregateError class. If a unresolved and by node_redis rejected command has no callback and
    this applies to more than a single command, the errors for the commands without callback are aggregated
    to a single error that is emitted in debug_mode in that case.
-  Added `message_buffer` / `pmessage_buffer` events. That event is always going to emit a buffer
 -  Listening to the `message` event at the same time is always going to return the same message as string
-  Added callback option to the duplicate function
-  Added support for `__proto__` and other reserved keywords as hgetall field
-  Updated [redis-commands](https://github.com/NodeRedis/redis-commands) dependency ([changelog](https://github.com/NodeRedis/redis-commands/releases/tag/v.1.2.0))
Bugfixes
-  Fixed v.2.5.0 auth command regression (under special circumstances a reconnect would not authenticate properly)
-  Fixed v.2.6.0-0 pub sub mode and quit command regressions:
 -  Entering pub sub mode not working if a earlier called and still running command returned an error
 -  Unsubscribe callback not called if unsubscribing from all channels and resubscribing right away
 -  Quit command resulting in an error in some cases
-  Fixed special handled functions in batch and multi context not working the same as without (e.g. select and info)
 -  Be aware that not all commands work in combination with transactions but they all work with batch
-  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
## v.2.6.0-1 - 01 Apr, 2016
A second pre-release with further fixes. This is likely going to be released as 2.6.0 stable without further changes.
Features
-  Added type validations for client.send_command arguments
Bugfixes
-  Fixed client.send_command not working properly with every command and every option
-  Fixed pub sub mode unsubscribing from all channels in combination with the new `string_numbers` option crashing
-  Fixed pub sub mode unsubscribing from all channels not respected while reconnecting
-  Fixed pub sub mode events in combination with the `string_numbers` option emitting the number of channels not as number
## v.2.6.0-0 - 27 Mar, 2016
This is mainly a very important bug fix release with some smaller features.
Features
-  Monitor and pub sub mode now work together with the offline queue
 -  All commands that were send after a connection loss are now going to be send after reconnecting
-  Activating monitor mode does now work together with arbitrary commands including pub sub mode
-  Pub sub mode is completly rewritten and all known issues fixed
-  Added `string_numbers` option to get back strings instead of numbers
-  Quit command is from now on always going to end the connection properly
Bugfixes
-  Fixed calling monitor command while other commands are still running
-  Fixed monitor and pub sub mode not working together
-  Fixed monitor mode not working in combination with the offline queue
-  Fixed pub sub mode not working in combination with the offline queue
-  Fixed pub sub mode resubscribing not working with non utf8 buffer channels
-  Fixed pub sub mode crashing if calling unsubscribe / subscribe in various combinations
-  Fixed pub sub mode emitting unsubscribe even if no channels were unsubscribed
-  Fixed pub sub mode emitting a message without a message published
-  Fixed quit command not ending the connection and resulting in further reconnection if called while reconnecting
The quit command did not end connections earlier if the connection was down at that time and this could have
lead to strange situations, therefor this was fixed to end the connection right away in those cases.
## v.2.5.3 - 21 Mar, 2016
Bugfixes
-  Revert throwing on invalid data types and print a warning instead
## v.2.5.2 - 16 Mar, 2016
Bugfixes
-  Fixed breaking changes against Redis 2.4 introduced in 2.5.0 / 2.5.1
## v.2.5.1 - 15 Mar, 2016
Bugfixes
-  Fixed info command not working anymore with optional section argument
## v.2.5.0 - 15 Mar, 2016
Same changelog as the pre-release
## v.2.5.0-1 - 07 Mar, 2016
This is a big release with some substaintual underlining changes. Therefor this is released as a pre-release and I encourage anyone who's able to, to test this out.
It took way to long to release this one and the next release cycles will be shorter again.
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).
Features
-  The parsers moved into the [redis-parser](https://github.com/NodeRedis/node-redis-parser) module and will be maintained in there from now on
 -  Improve js parser speed significantly for big SUNION/SINTER/LRANGE/ZRANGE
-  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)
-  Added a `retry_unfulfilled_commands` option
 -  Setting this to 'true' results in retrying all commands that were not fulfilled on a connection loss after the reconnect. Use with caution
-  Added a `db` option to select the database while connecting (this is [not recommended](https://groups.google.com/forum/#!topic/redis-db/vS5wX8X4Cjg))
-  Added a `password` option as alias for auth_pass
-  The client.server_info is from now on updated while using the info command
-  Gracefuly handle redis protocol errors from now on
-  Added a `warning` emitter that receives node_redis warnings like auth not required and deprecation messages
-  Added a `retry_strategy` option that replaces all reconnect options
-  The reconnecting event from now on also receives:
 -  The error message why the reconnect happend (params.error)
 -  The amount of times the client was connected (params.times_connected)
 -  The total reconnecting time since the last time connected (params.total_retry_time)
-  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))
-  redis.createClient is now checking input values stricter and detects more faulty input
-  Started refactoring internals into individual modules
-  Pipelining speed improvements
Bugfixes
-  Fixed explicit undefined as a command callback in a multi context
-  Fixed hmset failing to detect the first key as buffer or date if the key is of that type
-  Fixed do not run toString on an array argument and throw a "invalid data" error instead
 -  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
 -  The same applies to Map / Set and individual Object types
-  Fixed redis url not accepting the protocol being omitted or protocols other than the redis protocol for convienence
-  Fixed parsing the db keyspace even if the first database does not begin with a zero
-  Fixed handling of errors occuring while receiving pub sub messages
-  Fixed huge string pipelines crashing NodeJS (Pipeline size above 256mb)
-  Fixed rename_commands and prefix option not working together
-  Fixed ready being emitted to early in case a slave is still syncing / master down
Deprecations
-  Using any command with a argument being set to null or undefined is deprecated
 -  From v.3.0.0 on using a command with such an argument will return an error instead
 -  If you want to keep the old behavior please use a precheck in your code that converts the arguments to a string.
 -  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.
-  Using .end(flush) without the flush parameter is deprecated and the flush parameter should explicitly be used
 -  From v.3.0.0 on using .end without flush will result in an error
 -  Using .end without flush means that any command that did not yet return is going to silently fail. Therefor this is considered harmfull and you should explicitly silence such errors if you are sure you want this
-  Depending on the return value of a command to detect the backpressure is deprecated
 -  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
-  The `socket_nodelay` option is deprecated and will be removed in v.3.0.0
 -  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.
 -  If you are sure you want to activate the NAGLE algorithm you can still activate it by using client.stream.setNoDelay(false)
-  The `max_attempts` option is deprecated and will be removed in v.3.0.0. Please use the `retry_strategy` instead
-  The `retry_max_delay` option is deprecated and will be removed in v.3.0.0. Please use the `retry_strategy` instead
-  The drain event is deprecated and will be removed in v.3.0.0. Please listen to the stream drain event instead
-  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
-  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
-  Removed non documented command syntax (adding the callback to an arguments array instead of passing it as individual argument)
## v.2.4.2 - 27 Nov, 2015
Bugfixes
-  Fixed not emitting ready after reconnect with disable_resubscribing ([@maxgalbu](https://github.com/maxgalbu))
## v.2.4.1 - 25 Nov, 2015
Bugfixes
-  Fixed a js parser regression introduced in 2.4.0 ([@BridgeAR](https://github.com/BridgeAR))
## v.2.4.0 - 25 Nov, 2015
Features
-  Added `tls` option to initiate a connection to a redis server behind a TLS proxy. Thanks ([@paddybyers](https://github.com/paddybyers))
-  Added `prefix` option to auto key prefix any command with the provided prefix ([@luin](https://github.com/luin) & [@BridgeAR](https://github.com/BridgeAR))
-  Added `url` option to pass the connection url with the options object ([@BridgeAR](https://github.com/BridgeAR))
-  Added `client.duplicate([options])` to duplicate the current client and return a new one with the same options ([@BridgeAR](https://github.com/BridgeAR))
-  Improve performance by up to 20% on almost all use cases ([@BridgeAR](https://github.com/BridgeAR))
Bugfixes
-  Fixed js parser handling big values slow ([@BridgeAR](https://github.com/BridgeAR))
 -  The speed is now on par with the hiredis parser.
## v.2.3.1 - 18 Nov, 2015
Bugfixes
-  Fixed saving buffers with charsets other than utf-8 while using multi ([@BridgeAR](https://github.com/BridgeAR))
-  Fixed js parser handling big values very slow ([@BridgeAR](https://github.com/BridgeAR))
 -  The speed is up to ~500% faster than before but still up to ~50% slower than the hiredis parser.
## v.2.3.0 - 30 Oct, 2015
Features
-  Improve speed further for: ([@BridgeAR](https://github.com/BridgeAR))
 -  saving big strings (up to +300%)
 -  using .multi / .batch (up to +50% / on Node.js 0.10.x +300%)
 -  saving small buffers
-  Increased coverage to 99% ([@BridgeAR](https://github.com/BridgeAR))
-  Refactored manual backpressure control ([@BridgeAR](https://github.com/BridgeAR))
 -  Removed the high water mark and low water mark. Such a mechanism should be implemented by a user instead
 -  The `drain` event is from now on only emitted if the stream really had to buffer
-  Reduced the default connect_timeout to be one hour instead of 24h ([@BridgeAR](https://github.com/BridgeAR))
-  Added .path to redis.createClient(options); ([@BridgeAR](https://github.com/BridgeAR))
-  Ignore info command, if not available on server ([@ivanB1975](https://github.com/ivanB1975))
Bugfixes
-  Fixed a js parser error that could result in a timeout ([@BridgeAR](https://github.com/BridgeAR))
-  Fixed .multi / .batch used with Node.js 0.10.x not working properly after a reconnect ([@BridgeAR](https://github.com/BridgeAR))
-  Fixed fired but not yet returned commands not being rejected after a connection loss ([@BridgeAR](https://github.com/BridgeAR))
-  Fixed connect_timeout not respected if no connection has ever been established ([@gagle](https://github.com/gagle) & [@benjie](https://github.com/benjie))
-  Fixed return_buffers in pub sub mode ([@komachi](https://github.com/komachi))
## v.2.2.5 - 18 Oct, 2015
Bugfixes
-  Fixed undefined options passed to a new instance not accepted (possible with individual .createClient functions) ([@BridgeAR](https://github.com/BridgeAR))
## v.2.2.4 - 17 Oct, 2015
Bugfixes
-  Fixed unspecific error message for unresolvable commands ([@BridgeAR](https://github.com/BridgeAR))
-  Fixed not allowed command error in pubsub mode not being returned in a provided callback ([@BridgeAR](https://github.com/BridgeAR))
-  Fixed to many commands forbidden in pub sub mode ([@BridgeAR](https://github.com/BridgeAR))
-  Fixed mutation of the arguments array passed to .multi / .batch constructor ([@BridgeAR](https://github.com/BridgeAR))
-  Fixed mutation of the options object passed to createClient ([@BridgeAR](https://github.com/BridgeAR))
-  Fixed error callback in .multi not called if connection in broken mode ([@BridgeAR](https://github.com/BridgeAR))
## v.2.2.3 - 14 Oct, 2015
Bugfixes
-  Fixed multi not being executed on Node 0.10.x if node_redis not yet ready ([@BridgeAR](https://github.com/BridgeAR))
## v.2.2.2 - 14 Oct, 2015
Bugfixes
-  Fixed regular commands not being executed after a .multi until .exec was called ([@BridgeAR](https://github.com/BridgeAR))
## v.2.2.1 - 12 Oct, 2015
No code change
## v.2.2.0 - 12 Oct, 2015 - The peregrino falcon
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.
Features
-  Added rename_commands options to handle renamed commands from the redis config ([@digmxl](https://github.com/digmxl) & [@BridgeAR](https://github.com/BridgeAR))
-  Added disable_resubscribing option to prevent a client from resubscribing after reconnecting ([@BridgeAR](https://github.com/BridgeAR))
-  Increased performance ([@BridgeAR](https://github.com/BridgeAR))
 -  exchanging built in queue with [@petkaantonov](https://github.com/petkaantonov)'s [double-ended queue](https://github.com/petkaantonov/deque)
 -  prevent polymorphism
 -  optimize statements
-  Added *.batch* command, similar to .multi but without transaction ([@BridgeAR](https://github.com/BridgeAR))
-  Improved pipelining to minimize the [RTT](http://redis.io/topics/pipelining) further ([@BridgeAR](https://github.com/BridgeAR))
Bugfixes
-  Fixed a javascript parser regression introduced in 2.0 that could result in timeouts on high load. ([@BridgeAR](https://github.com/BridgeAR))
  - 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 :)
-  Fixed should_buffer boolean for .exec, .select and .auth commands not being returned and fix a couple special conditions ([@BridgeAR](https://github.com/BridgeAR))
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>
Both .multi and .batch are from now on going to cache the commands and release them while calling .exec.
Please consider using .batch instead of looping through a lot of commands one by one. This will significantly improve your performance.
Here are some stats compared to ioredis 1.9.1 (Lenovo T450s i7-5600U):
                      simple set
          82,496 op/s » ioredis
         112,617 op/s » node_redis
                      simple get
          82,015 op/s » ioredis
         105,701 op/s » node_redis
                      simple get with pipeline
          10,233 op/s » ioredis
          26,541 op/s » node_redis (using .batch)
                      lrange 100
           7,321 op/s » ioredis
          26,155 op/s » node_redis
                      publish
          90,524 op/s » ioredis
         112,823 op/s » node_redis
                      subscribe
          43,783 op/s » ioredis
          61,889 op/s » node_redis
To conclude: we can proudly say that node_redis is very likely outperforming any other node redis client.
Known issues
-  The pub sub system has some flaws and those will be addressed in the next minor release
## v2.1.0 - Oct 02, 2015
Features:
-  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)
-  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)
-  Speedup common cases (@BridgeAR)
Bugfixes:
-  Fix argument mutation while using the array notation with the multi constructor (@BridgeAR)
-  Fix multi.hmset key not being type converted if used with an object and key not being a string (@BridgeAR)
-  Fix parser errors not being catched properly (@BridgeAR)
-  Fix a crash that could occur if a redis server does not return the info command as usual #541 (@BridgeAR)
-  Explicitly passing undefined as a callback statement will work again. E.g. client.publish('channel', 'message', undefined); (@BridgeAR)
## v2.0.1 - Sep 24, 2015
Bugfixes:
-  Fix argument mutation while using the array notation in combination with keys / callbacks ([#866](.)). (@BridgeAR)
## v2.0.0 - Sep 21, 2015
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.
# What's new in 2.0
- Implemented a "connection is broken" mode if no connection could be established
- node_redis no longer throws under any circumstances, preventing it from terminating applications.
- Multi error handling is now working properly
- Consistent command behavior including multi
- Windows support
- Improved performance
- A lot of code cleanup
- Many bug fixes
- Better user support!
## Features:
- Added a "redis connection is broken" mode after reaching max connection attempts / exceeding connection timeout. (@BridgeAR)
- Added NODE_DEBUG=redis env to activate the debug_mode (@BridgeAR)
- Added a default connection timeout of 24h instead of never timing out as a default (@BridgeAR)
- Added: Network errors and other stream errors will from now on include the error code as `err.code` property (@BridgeAR)
- Added: Errors thrown by redis will now include the redis error code as `err.code` property. (@skeggse & @BridgeAR)
- Added: Errors thrown by node_redis will now include a `err.command` property for the command used (@BridgeAR)
- Added new commands and drop support for deprecated *substr* (@BridgeAR)
- Added new possibilities how to provide the command arguments (@BridgeAR)
- The entries in the keyspace of the server_info is now an object instead of a string. (@SinisterLight & @BridgeAR)
- Small speedup here and there (e.g. by not using .toLowerCase() anymore) (@BridgeAR)
- Full windows support (@bcoe)
- Increased coverage by 10% and add a lot of tests to make sure everything works as it should. We now reached 97% :-) (@BridgeAR)
- Remove dead code, clean up and refactor very old chunks (@BridgeAR)
- Don't flush the offline queue if reconnecting (@BridgeAR)
- Emit all errors insteaf of throwing sometimes and sometimes emitting them (@BridgeAR)
- *auth_pass* passwords are now checked to be a valid password (@jcppman & @BridgeAR)
## Bug fixes:
- Don't kill the app anymore by randomly throwing errors sync instead of emitting them (@BridgeAR)
- Don't catch user errors anymore occuring in callbacks (no try callback anymore & more fixes for the parser) (@BridgeAR)
- Early garbage collection of queued items (@dohse)
- Fix js parser returning errors as strings (@BridgeAR)
- Do not wrap errors into other errors (@BridgeAR)
- Authentication failures are now returned in the callback instead of being emitted (@BridgeAR)
- Fix a memory leak on reconnect (@rahar)
- Using `send_command` directly may now also be called without the args as stated in the [README.md](./README.md) (@BridgeAR)
- Fix the multi.exec error handling (@BridgeAR)
- Fix commands being inconsistent and behaving wrong (@BridgeAR)
- Channel names with spaces are now properly resubscribed after a reconnection (@pbihler)
- Do not try to reconnect after the connection timeout has been exceeded (@BridgeAR)
- Ensure the execution order is observed if using .eval (@BridgeAR)
- Fix commands not being rejected after calling .quit (@BridgeAR)
- Fix .auth calling the callback twice if already connected (@BridgeAR)
- Fix detect_buffers not working in pub sub mode and while monitoring (@BridgeAR)
- Fix channel names always being strings instead of buffers while return_buffers is true (@BridgeAR)
- Don't print any debug statements if not asked for (@BridgeAR)
- Fix a couple small other bugs
## Breaking changes:
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`.
2. Error messages have changed quite a bit. If you depend on a specific wording please check your application carfully.
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).
4. The Multi error handling has changed a lot!
 - All errors are from now on errors instead of strings (this only applied to the js parser).
 - 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.
 - 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`).
 - 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!
 - If `multi.exec` does not have a callback and an EXECABORT error occurrs, it'll emit that error instead.
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.
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.
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.
8. The keyspace of `redis.server_info` (db0...) is from now on an object instead of an string.
NodeRedis also thanks @qdb, @tobek, @cvibhagool, @frewsxcv, @davidbanham, @serv, @vitaliylag, @chrishamant, @GamingCoder and all other contributors that I may have missed for their contributions!
From now on we'll push new releases more frequently out and fix further long outstanding things and implement new features.
<hr>
## v1.0.0 - Aug 30, 2015
* Huge issue and pull-request cleanup. Thanks Blain! (@blainsmith)
* [#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)
* [#749](https://github.com/NodeRedis/node_redis/pull/749) Fix reconnection bug when client is in monitoring mode (@danielbprice)
* [#786](https://github.com/NodeRedis/node_redis/pull/786) Refactor createClient. Fixes #651 (@BridgeAR)
* [#793](https://github.com/NodeRedis/node_redis/pull/793) Refactor tests and improve test coverage (@erinspice, @bcoe)
* [#733](https://github.com/NodeRedis/node_redis/pull/733) Fixes detect_buffers functionality in the context of exec. Fixes #732, #263 (@raydog)
* [#785](https://github.com/NodeRedis/node_redis/pull/785) Tiny speedup by using 'use strict' (@BridgeAR)
* Fix extraneous error output due to pubsub tests (Mikael Kohlmyr)
## v0.12.1 - Aug 10, 2014
* Fix IPv6/IPv4 family selection in node 0.11+ (Various)
## v0.12.0 - Aug 9, 2014
* Fix unix socket support (Jack Tang)
* Improve createClient argument handling (Jack Tang)
## v0.11.0 - Jul 10, 2014
* IPv6 Support. (Yann Stephan)
* Revert error emitting and go back to throwing errors. (Bryce Baril)
* Set socket_keepalive to prevent long-lived client timeouts. (mohit)
* Correctly reset retry timer. (ouotuo)
* Domains protection from bad user exit. (Jake Verbaten)
* Fix reconnection socket logic to prevent misqueued entries. (Iain Proctor)
## v0.10.3 - May 22, 2014
* Update command list to match Redis 2.8.9 (Charles Feng)
## v0.10.2 - May 18, 2014
* Better binary key handling for HGETALL. (Nick Apperson)
* Fix test not resetting `error` handler. (CrypticSwarm)
* Fix SELECT error semantics. (Bryan English)
## v0.10.1 - February 17, 2014
* Skip plucking redis version from the INFO stream if INFO results weren't provided. (Robert Sköld)
## v0.10.0 - December 21, 2013
* Instead of throwing errors asynchronously, emit errors on client. (Bryce Baril)
## v0.9.2 - December 15, 2013
* Regenerate commands for new 2.8.x Redis commands. (Marek Ventur)
* Correctly time reconnect counts when using 'auth'. (William Hockey)
## v0.9.1 - November 23, 2013
* Allow hmset to accept numeric keys. (Alex Stokes)
* Fix TypeError for multiple MULTI/EXEC errors. (Kwangsu Kim)
## v0.9.0 - October 17, 2013
* Domains support. (Forrest L Norvell)
## v0.8.6 - October 2, 2013
* If error is already an Error, don't wrap it in another Error. (Mathieu M-Gosselin)
* Fix retry delay logic (Ian Babrou)
* Return Errors instead of strings where Errors are expected (Ian Babrou)
* Add experimental `.unref()` method to RedisClient (Bryce Baril / Olivier Lalonde)
* Strengthen checking of reply to prevent conflating "message" or "pmessage" fields with pub_sub replies. (Bryce Baril)
## v0.8.5 - September 26, 2013
* Add `auth_pass` option to connect and immediately authenticate (Henrik Peinar)
## v0.8.4 - June 24, 2013
Many contributed features and fixes, including:
* Ignore password set if not needed. (jbergknoff)
* Improved compatibility with 0.10.X for tests and client.end() (Bryce Baril)
* Protect connection retries from application exceptions. (Amos Barreto)
* Better exception handling for Multi/Exec (Thanasis Polychronakis)
* Renamed pubsub mode to subscriber mode (Luke Plaster)
* Treat SREM like SADD when passed an array (Martin Ciparelli)
* Fix empty unsub/punsub TypeError (Jeff Barczewski)
* Only attempt to run a callback if it one was provided (jifeng)
## v0.8.3 - April 09, 2013
Many contributed features and fixes, including:
* Fix some tests for Node.js version 0.9.x+ changes (Roman Ivanilov)
* Fix error when commands submitted after idle event handler (roamm)
* Bypass Redis for no-op SET/SETEX commands (jifeng)
* Fix HMGET + detect_buffers (Joffrey F)
* Fix CLIENT LOAD functionality (Jonas Dohse)
* Add percentage outputs to diff_multi_bench_output.js (Bryce Baril)
* Add retry_max_delay option (Tomasz Durka)
* Fix parser off-by-one errors with nested multi-bulk replies (Bryce Baril)
* Prevent parser from sinking application-side exceptions (Bryce Baril)
* Fix parser incorrect buffer skip when parsing multi-bulk errors (Bryce Baril)
* Reverted previous change with throwing on non-string values with HMSET (David Trejo)
* Fix command queue sync issue when using pubsub (Tom Leach)
* Fix compatibility with two-word Redis commands (Jonas Dohse)
* Add EVAL with array syntax (dmoena)
* Fix tests due to Redis reply order changes in 2.6.5+ (Bryce Baril)
* Added a test for the SLOWLOG command (Nitesh Sinha)
* Fix SMEMBERS order dependency in test broken by Redis changes (Garrett Johnson)
* Update commands for new Redis commands (David Trejo)
* Prevent exception from SELECT on subscriber reconnection (roamm)
## v0.8.2 - November 11, 2012
Another version bump because 0.8.1 didn't get applied properly for some mysterious reason.
Sorry about that.
Changed name of "faster" parser to "javascript".
## v0.8.1 - September 11, 2012
Important bug fix for null responses (Jerry Sievert)
## v0.8.0 - September 10, 2012
Many contributed features and fixes, including:
* Pure JavaScript reply parser that is usually faster than hiredis (Jerry Sievert)
* Remove hiredis as optionalDependency from package.json. It still works if you want it.
* Restore client state on reconnect, including select, subscribe, and monitor. (Ignacio Burgueño)
* Fix idle event (Trae Robrock)
* Many documentation improvements and bug fixes (David Trejo)
## v0.7.2 - April 29, 2012
Many contributed fixes. Thank you, contributors.
* [GH-190] - pub/sub mode fix (Brian Noguchi)
* [GH-165] - parser selection fix (TEHEK)
* numerous documentation and examples updates
* auth errors emit Errors instead of Strings (David Trejo)
## v0.7.1 - November 15, 2011
Fix regression in reconnect logic.
Very much need automated tests for reconnection and queue logic.
## v0.7.0 - November 14, 2011
Many contributed fixes. Thanks everybody.
* [GH-127] - properly re-initialize parser on reconnect
* [GH-136] - handle passing undefined as callback (Ian Babrou)
* [GH-139] - properly handle exceptions thrown in pub/sub event handlers (Felix Geisendörfer)
* [GH-141] - detect closing state on stream error (Felix Geisendörfer)
* [GH-142] - re-select database on reconnection (Jean-Hugues Pinson)
* [GH-146] - add sort example (Maksim Lin)
Some more goodies:
* Fix bugs with node 0.6
* Performance improvements
* New version of `multi_bench.js` that tests more realistic scenarios
* [GH-140] - support optional callback for subscribe commands
* Properly flush and error out command queue when connection fails
* Initial work on reconnection thresholds
## v0.6.7 - July 30, 2011
(accidentally skipped v0.6.6)
Fix and test for [GH-123]
Passing an Array as as the last argument should expand as users
expect.  The old behavior was to coerce the arguments into Strings,
which did surprising things with Arrays.
## v0.6.5 - July 6, 2011
Contributed changes:
*  Support SlowBuffers (Umair Siddique)
*  Add Multi to exports (Louis-Philippe Perron)
*  Fix for drain event calculation (Vladimir Dronnikov)
Thanks!
## v0.6.4 - June 30, 2011
Fix bug with optional callbacks for hmset.
## v0.6.2 - June 30, 2011
Bugs fixed:
*  authentication retry while server is loading db (danmaz74) [GH-101]
*  command arguments processing issue with arrays
New features:
*  Auto update of new commands from redis.io (Dave Hoover)
*  Performance improvements and backpressure controls.
*  Commands now return the true/false value from the underlying socket write(s).
*  Implement command_queue high water and low water for more better control of queueing.
See `examples/backpressure_drain.js` for more information.
## v0.6.1 - June 29, 2011
Add support and tests for Redis scripting through EXEC command.
Bug fix for monitor mode.  (forddg)
Auto update of new commands from redis.io (Dave Hoover)
## v0.6.0 - April 21, 2011
Lots of bugs fixed.
*  connection error did not properly trigger reconnection logic [GH-85]
*  client.hmget(key, [val1, val2]) was not expanding properly [GH-66]
*  client.quit() while in pub/sub mode would throw an error [GH-87]
*  client.multi(['hmset', 'key', {foo: 'bar'}]) fails [GH-92]
*  unsubscribe before subscribe would make things very confused [GH-88]
*  Add BRPOPLPUSH [GH-79]
## v0.5.11 - April 7, 2011
Added DISCARD
I originally didn't think DISCARD would do anything here because of the clever MULTI interface, but somebody
pointed out to me that DISCARD can be used to flush the WATCH set.
## v0.5.10 - April 6, 2011
Added HVALS
## v0.5.9 - March 14, 2011
Fix bug with empty Array arguments - Andy Ray
## v0.5.8 - March 14, 2011
Add `MONITOR` command and special monitor command reply parsing.
## v0.5.7 - February 27, 2011
Add magical auth command.
Authentication is now remembered by the client and will be automatically sent to the server
on every connection, including any reconnections.
## v0.5.6 - February 22, 2011
Fix bug in ready check with `return_buffers` set to `true`.
Thanks to Dean Mao and Austin Chau.
## v0.5.5 - February 16, 2011
Add probe for server readiness.
When a Redis server starts up, it might take a while to load the dataset into memory.
During this time, the server will accept connections, but will return errors for all non-INFO
commands.  Now node_redis will send an INFO command whenever it connects to a server.
If the info command indicates that the server is not ready, the client will keep trying until
the server is ready.  Once it is ready, the client will emit a "ready" event as well as the
"connect" event.  The client will queue up all commands sent before the server is ready, just
like it did before.  When the server is ready, all offline/non-ready commands will be replayed.
This should be backward compatible with previous versions.
To disable this ready check behavior, set `options.no_ready_check` when creating the client.
As a side effect of this change, the key/val params from the info command are available as
`client.server_options`.  Further, the version string is decomposed into individual elements
in `client.server_options.versions`.
## v0.5.4 - February 11, 2011
Fix excess memory consumption from Queue backing store.
Thanks to Gustaf Sjöberg.
## v0.5.3 - February 5, 2011
Fix multi/exec error reply callback logic.
Thanks to Stella Laurenzo.
## v0.5.2 - January 18, 2011
Fix bug where unhandled error replies confuse the parser.
## v0.5.1 - January 18, 2011
Fix bug where subscribe commands would not handle redis-server startup error properly.
## v0.5.0 - December 29, 2010
Some bug fixes:
* An important bug fix in reconnection logic.  Previously, reply callbacks would be invoked twice after
  a reconnect.
* Changed error callback argument to be an actual Error object.
New feature:
* Add friendly syntax for HMSET using an object.
## v0.4.1 - December 8, 2010
Remove warning about missing hiredis.  You probably do want it though.
## v0.4.0 - December 5, 2010
Support for multiple response parsers and hiredis C library from Pieter Noordhuis.
Return Strings instead of Buffers by default.
Empty nested mb reply bug fix.
## v0.3.9 - November 30, 2010
Fix parser bug on failed EXECs.
## v0.3.8 - November 10, 2010
Fix for null MULTI response when WATCH condition fails.
## v0.3.7 - November 9, 2010
Add "drain" and "idle" events.
## v0.3.6 - November 3, 2010
Add all known Redis commands from Redis master, even ones that are coming in 2.2 and beyond.
Send a friendlier "error" event message on stream errors like connection refused / reset.
## v0.3.5 - October 21, 2010
A few bug fixes.
* Fixed bug with `nil` multi-bulk reply lengths that showed up with `BLPOP` timeouts.
* Only emit `end` once when connection goes away.
* Fixed bug in `test.js` where driver finished before all tests completed.
## unversioned wasteland
See the git history for what happened before.

+ 0 - 80
src/doctor/node_modules/redis/connection_breaker.js

@ -1,80 +0,0 @@
var net = require('net');
var proxyPort = 6379;
var counter = 0;
function breaker(conn) {
    conn.end();
    conn.destroy();
}
var server = net.createServer(function(conn) {
    counter++;
    var proxyConn = net.createConnection({
        port: proxyPort
    });
    conn.pipe(proxyConn);
    proxyConn.pipe(conn);
    proxyConn.on('end', function() {
        conn.end();
    });
    conn.on('end', function() {
        proxyConn.end();
    });
    conn.on('close', function() {
        proxyConn.end();
    });
    proxyConn.on('close', function() {
        conn.end();
    });
    proxyConn.on('error', function() {
        conn.end();
    });
    conn.on('error', function() {
        proxyConn.end();
    });
    setTimeout(breaker.bind(null, conn), Math.floor(Math.random() * 2000));
});
server.listen(6479);
var redis = require('./');
var port = 6479;
var client = redis.createClient(6479, 'localhost');
function iter() {
    var k = "k" + Math.floor(Math.random() * 10);
    var coinflip = Math.random() > 0.5;
    if (coinflip) {
        client.set(k, k, function(err, resp) {
            if (!err && resp !== "OK") {
                console.log("Unexpected set response " + resp);
            }
        });
    } else {
        client.get(k, function(err, resp) {
            if (!err) {
                if (k !== resp) {
                    console.log("Key response mismatch: " + k + " " + resp);
                }
            }
        });
    }
}
function iters() {
    for (var i = 0; i < 100; ++i) {
        iter();
    }
    setTimeout(iters, 10);
}
client.on("connect", function () {
    iters();
});
client.on("error", function (err) {
    console.log("Client error " + err);
});

+ 864 - 1055
src/doctor/node_modules/redis/index.js

@ -1,1284 +1,1093 @@
/*global Buffer require exports console setTimeout */
var net = require("net"),
    util = require("./lib/util"),
    Queue = require("./lib/queue"),
    to_array = require("./lib/to_array"),
    events = require("events"),
    crypto = require("crypto"),
    parsers = [], commands,
    connection_id = 0,
    default_port = 6379,
    default_host = "127.0.0.1";
// can set this to true to enable for all connections
exports.debug_mode = false;
var arraySlice = Array.prototype.slice
function trace() {
    if (!exports.debug_mode) return;
    console.log.apply(null, arraySlice.call(arguments))
'use strict';
var net = require('net');
var tls = require('tls');
var util = require('util');
var utils = require('./lib/utils');
var Command = require('./lib/command');
var Queue = require('double-ended-queue');
var errorClasses = require('./lib/customErrors');
var EventEmitter = require('events');
var Parser = require('redis-parser');
var commands = require('redis-commands');
var debug = require('./lib/debug');
var unifyOptions = require('./lib/createClient');
var SUBSCRIBE_COMMANDS = {
    subscribe: true,
    unsubscribe: true,
    psubscribe: true,
    punsubscribe: true
};
// Newer Node.js versions > 0.10 return the EventEmitter right away and using .EventEmitter was deprecated
if (typeof EventEmitter !== 'function') {
    EventEmitter = EventEmitter.EventEmitter;
}
// hiredis might not be installed
try {
    require("./lib/parser/hiredis");
    parsers.push(require("./lib/parser/hiredis"));
} catch (err) {
    if (exports.debug_mode) {
        console.warn("hiredis parser not installed.");
function noop () {}
function handle_detect_buffers_reply (reply, command, buffer_args) {
    if (buffer_args === false || this.message_buffers) {
        // If detect_buffers option was specified, then the reply from the parser will be a buffer.
        // If this command did not use Buffer arguments, then convert the reply to Strings here.
        reply = utils.reply_to_strings(reply);
    }
    if (command === 'hgetall') {
        reply = utils.reply_to_object(reply);
    }
    return reply;
}
parsers.push(require("./lib/parser/javascript"));
exports.debug_mode = /\bredis\b/i.test(process.env.NODE_DEBUG);
function RedisClient(stream, options) {
    this.stream = stream;
    this.options = options = options || {};
// Attention: The second parameter might be removed at will and is not officially supported.
// Do not rely on this
function RedisClient (options, stream) {
    // Copy the options so they are not mutated
    options = utils.clone(options);
    EventEmitter.call(this);
    var cnx_options = {};
    var self = this;
    /* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
    for (var tls_option in options.tls) {
        cnx_options[tls_option] = options.tls[tls_option];
        // Copy the tls options into the general options to make sure the address is set right
        if (tls_option === 'port' || tls_option === 'host' || tls_option === 'path' || tls_option === 'family') {
            options[tls_option] = options.tls[tls_option];
        }
    }
    if (stream) {
        // The stream from the outside is used so no connection from this side is triggered but from the server this client should talk to
        // Reconnect etc won't work with this. This requires monkey patching to work, so it is not officially supported
        options.stream = stream;
        this.address = '"Private stream"';
    } else if (options.path) {
        cnx_options.path = options.path;
        this.address = options.path;
    } else {
        cnx_options.port = +options.port || 6379;
        cnx_options.host = options.host || '127.0.0.1';
        cnx_options.family = (!options.family && net.isIP(cnx_options.host)) || (options.family === 'IPv6' ? 6 : 4);
        this.address = cnx_options.host + ':' + cnx_options.port;
    }
    // Warn on misusing deprecated functions
    if (typeof options.retry_strategy === 'function') {
        if ('max_attempts' in options) {
            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.');
            // Do not print deprecation warnings twice
            delete options.max_attempts;
        }
        if ('retry_max_delay' in options) {
            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.');
            // Do not print deprecation warnings twice
            delete options.retry_max_delay;
        }
    }
    this.connection_id = ++connection_id;
    this.connection_options = cnx_options;
    this.connection_id = RedisClient.connection_id++;
    this.connected = false;
    this.ready = false;
    this.connections = 0;
    if (this.options.socket_nodelay === undefined) {
        this.options.socket_nodelay = true;
    }
    if (this.options.socket_keepalive === undefined) {
        this.options.socket_keepalive = true;
    if (options.socket_nodelay === undefined) {
        options.socket_nodelay = true;
    } else if (!options.socket_nodelay) { // Only warn users with this set to false
        self.warn(
            'socket_nodelay is deprecated and will be removed in v.3.0.0.\n' +
            'Setting socket_nodelay to false likely results in a reduced throughput. Please use .batch for pipelining instead.\n' +
            'If you are sure you rely on the NAGLE-algorithm you can activate it by calling client.stream.setNoDelay(false) instead.'
        );
    }
    if (options.socket_keepalive === undefined) {
        options.socket_keepalive = true;
    }
    for (var command in options.rename_commands) {
        options.rename_commands[command.toLowerCase()] = options.rename_commands[command];
    }
    options.return_buffers = !!options.return_buffers;
    options.detect_buffers = !!options.detect_buffers;
    // Override the detect_buffers setting if return_buffers is active and print a warning
    if (options.return_buffers && options.detect_buffers) {
        self.warn('WARNING: You activated return_buffers and detect_buffers at the same time. The return value is always going to be a buffer.');
        options.detect_buffers = false;
    }
    if (options.detect_buffers) {
        // We only need to look at the arguments if we do not know what we have to return
        this.handle_reply = handle_detect_buffers_reply;
    }
    this.should_buffer = false;
    this.command_queue_high_water = this.options.command_queue_high_water || 1000;
    this.command_queue_low_water = this.options.command_queue_low_water || 0;
    this.max_attempts = null;
    if (options.max_attempts && !isNaN(options.max_attempts) && options.max_attempts > 0) {
        this.max_attempts = +options.max_attempts;
    this.max_attempts = options.max_attempts | 0;
    if ('max_attempts' in options) {
        self.warn(
            'max_attempts is deprecated and will be removed in v.3.0.0.\n' +
            'To reduce the amount of options and the improve the reconnection handling please use the new `retry_strategy` option instead.\n' +
            'This replaces the max_attempts and retry_max_delay option.'
        );
    }
    this.command_queue = new Queue(); // Holds sent commands to de-pipeline them
    this.offline_queue = new Queue(); // Holds commands issued but not able to be sent
    this.pipeline_queue = new Queue(); // Holds all pipelined commands
    // ATTENTION: connect_timeout should change in v.3.0 so it does not count towards ending reconnection attempts after x seconds
    // This should be done by the retry_strategy. Instead it should only be the timeout for connecting to redis
    this.connect_timeout = +options.connect_timeout || 3600000; // 60 * 60 * 1000 ms
    this.enable_offline_queue = options.enable_offline_queue === false ? false : true;
    this.retry_max_delay = +options.retry_max_delay || null;
    if ('retry_max_delay' in options) {
        self.warn(
            'retry_max_delay is deprecated and will be removed in v.3.0.0.\n' +
            'To reduce the amount of options and the improve the reconnection handling please use the new `retry_strategy` option instead.\n' +
            'This replaces the max_attempts and retry_max_delay option.'
        );
    }
    this.command_queue = new Queue(); // holds sent commands to de-pipeline them
    this.offline_queue = new Queue(); // holds commands issued but not able to be sent
    this.commands_sent = 0;
    this.connect_timeout = false;
    if (options.connect_timeout && !isNaN(options.connect_timeout) && options.connect_timeout > 0) {
        this.connect_timeout = +options.connect_timeout;
    }
    this.enable_offline_queue = true;
    if (typeof this.options.enable_offline_queue === "boolean") {
        this.enable_offline_queue = this.options.enable_offline_queue;
    }
    this.retry_max_delay = null;
    if (options.retry_max_delay !== undefined && !isNaN(options.retry_max_delay) && options.retry_max_delay > 0) {
        this.retry_max_delay = options.retry_max_delay;
    }
    this.initialize_retry_vars();
    this.pub_sub_mode = false;
    this.pub_sub_mode = 0;
    this.subscription_set = {};
    this.monitoring = false;
    this.message_buffers = false;
    this.closing = false;
    this.server_info = {};
    this.auth_pass = null;
    if (options.auth_pass !== undefined) {
        this.auth_pass = options.auth_pass;
    }
    this.parser_module = null;
    this.selected_db = null;	// save the selected db here, used when reconnecting
    this.auth_pass = options.auth_pass || options.password;
    this.selected_db = options.db; // Save the selected db here, used when reconnecting
    this.old_state = null;
    this.fire_strings = true; // Determine if strings or buffers should be written to the stream
    this.pipeline = false;
    this.sub_commands_left = 0;
    this.times_connected = 0;
    this.buffers = options.return_buffers || options.detect_buffers;
    this.options = options;
    this.reply = 'ON'; // Returning replies is the default
    // Init parser
    this.reply_parser = create_parser(this);
    this.create_stream();
    // The listeners will not be attached right away, so let's print the deprecation message while the listener is attached
    this.on('newListener', function (event) {
        if (event === 'idle') {
            this.warn(
                'The idle event listener is deprecated and will likely be removed in v.3.0.0.\n' +
                'If you rely on this feature please open a new ticket in node_redis with your use case'
            );
        } else if (event === 'drain') {
            this.warn(
                'The drain event listener is deprecated and will be removed in v.3.0.0.\n' +
                'If you want to keep on listening to this event please listen to the stream drain event directly.'
            );
        } else if (event === 'message_buffer' || event === 'pmessage_buffer' || event === 'messageBuffer' || event === 'pmessageBuffer' && !this.buffers) {
            this.message_buffers = true;
            this.handle_reply = handle_detect_buffers_reply;
            this.reply_parser = create_parser(this);
        }
    });
}
util.inherits(RedisClient, EventEmitter);
RedisClient.connection_id = 0;
function create_parser (self) {
    return new Parser({
        returnReply: function (data) {
            self.return_reply(data);
        },
        returnError: function (err) {
            // Return a ReplyError to indicate Redis returned an error
            self.return_error(err);
        },
        returnFatalError: function (err) {
            // Error out all fired commands. Otherwise they might rely on faulty data. We have to reconnect to get in a working state again
            // Note: the execution order is important. First flush and emit, then create the stream
            err.message += '. Please report this.';
            self.ready = false;
            self.flush_and_error({
                message: 'Fatal error encountert. Command aborted.',
                code: 'NR_FATAL'
            }, {
                error: err,
                queues: ['command_queue']
            });
            self.emit('error', err);
            self.create_stream();
        },
        returnBuffers: self.buffers || self.message_buffers,
        name: self.options.parser || 'javascript',
        stringNumbers: self.options.string_numbers || false
    });
}
    this.install_stream_listeners();
/******************************************************************************
    events.EventEmitter.call(this);
}
util.inherits(RedisClient, events.EventEmitter);
exports.RedisClient = RedisClient;
    All functions in here are internal besides the RedisClient constructor
    and the exported functions. Don't rely on them as they will be private
    functions in node_redis v.3
RedisClient.prototype.install_stream_listeners = function() {
******************************************************************************/
// Attention: the function name "create_stream" should not be changed, as other libraries need this to mock the stream (e.g. fakeredis)
RedisClient.prototype.create_stream = function () {
    var self = this;
    this.stream.on("connect", function () {
    if (this.options.stream) {
        // Only add the listeners once in case of a reconnect try (that won't work)
        if (this.stream) {
            return;
        }
        this.stream = this.options.stream;
    } else {
        // On a reconnect destroy the former stream and retry
        if (this.stream) {
            this.stream.removeAllListeners();
            this.stream.destroy();
        }
        /* istanbul ignore if: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
        if (this.options.tls) {
            this.stream = tls.connect(this.connection_options);
        } else {
            this.stream = net.createConnection(this.connection_options);
        }
    }
    if (this.options.connect_timeout) {
        this.stream.setTimeout(this.connect_timeout, function () {
            // Note: This is only tested if a internet connection is established
            self.retry_totaltime = self.connect_timeout;
            self.connection_gone('timeout');
        });
    }
    /* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
    var connect_event = this.options.tls ? 'secureConnect' : 'connect';
    this.stream.once(connect_event, function () {
        this.removeAllListeners('timeout');
        self.times_connected++;
        self.on_connect();
    });
    this.stream.on("data", function (buffer_from_socket) {
        self.on_data(buffer_from_socket);
    this.stream.on('data', function (buffer_from_socket) {
        // The buffer_from_socket.toString() has a significant impact on big chunks and therefore this should only be used if necessary
        debug('Net read ' + self.address + ' id ' + self.connection_id); // + ': ' + buffer_from_socket.toString());
        self.reply_parser.execute(buffer_from_socket);
        self.emit_idle();
    });
    this.stream.on('error', function (err) {
        self.on_error(err);
    });
    this.stream.on("error", function (msg) {
        self.on_error(msg.message);
    /* istanbul ignore next: difficult to test and not important as long as we keep this listener */
    this.stream.on('clientError', function (err) {
        debug('clientError occured');
        self.on_error(err);
    });
    this.stream.on("close", function () {
        self.connection_gone("close");
    this.stream.once('close', function (hadError) {
        self.connection_gone('close');
    });
    this.stream.on("end", function () {
        self.connection_gone("end");
    this.stream.once('end', function () {
        self.connection_gone('end');
    });
    this.stream.on("drain", function () {
        self.should_buffer = false;
        self.emit("drain");
    this.stream.on('drain', function () {
        self.drain();
    });
    if (this.options.socket_nodelay) {
        this.stream.setNoDelay();
    }
    // Fire the command before redis is connected to be sure it's the first fired command
    if (this.auth_pass !== undefined) {
        this.ready = true;
        this.auth(this.auth_pass);
        this.ready = false;
    }
};
RedisClient.prototype.handle_reply = function (reply, command) {
    if (command === 'hgetall') {
        reply = utils.reply_to_object(reply);
    }
    return reply;
};
RedisClient.prototype.cork = noop;
RedisClient.prototype.uncork = noop;
RedisClient.prototype.initialize_retry_vars = function () {
    this.retry_timer = null;
    this.retry_totaltime = 0;
    this.retry_delay = 150;
    this.retry_backoff = 1;//1.7;
    this.retry_delay = 200;
    this.retry_backoff = 1.7;
    this.attempts = 1;
};
RedisClient.prototype.unref = function () {
    trace("User requesting to unref the connection");
    if (this.connected) {
        trace("unref'ing the socket connection");
        this.stream.unref();
    }
    else {
        trace("Not connected yet, will unref later");
        this.once("connect", function () {
            this.unref();
        })
    }
RedisClient.prototype.warn = function (msg) {
    var self = this;
    // Warn on the next tick. Otherwise no event listener can be added
    // for warnings that are emitted in the redis client constructor
    process.nextTick(function () {
        if (self.listeners('warning').length !== 0) {
            self.emit('warning', msg);
        } else {
            console.warn('node_redis:', msg);
        }
    });
};
// flush offline_queue and command_queue, erroring any items with a callback first
RedisClient.prototype.flush_and_error = function (message) {
    var command_obj, error;
    error = new Error(message);
    while (this.offline_queue.length > 0) {
        command_obj = this.offline_queue.shift();
        if (typeof command_obj.callback === "function") {
            try {
                command_obj.callback(error);
            } catch (callback_err) {
                process.nextTick(function () {
                    throw callback_err;
                });
// Flush provided queues, erroring any items with a callback first
RedisClient.prototype.flush_and_error = function (error_attributes, options) {
    options = options || {};
    var aggregated_errors = [];
    var queue_names = options.queues || ['command_queue', 'offline_queue']; // Flush the command_queue first to keep the order intakt
    for (var i = 0; i < queue_names.length; i++) {
        // If the command was fired it might have been processed so far
        if (queue_names[i] === 'command_queue') {
            error_attributes.message += ' It might have been processed.';
        } else { // As the command_queue is flushed first, remove this for the offline queue
            error_attributes.message = error_attributes.message.replace(' It might have been processed.', '');
        }
        // Don't flush everything from the queue
        for (var command_obj = this[queue_names[i]].shift(); command_obj; command_obj = this[queue_names[i]].shift()) {
            var err = new errorClasses.AbortError(error_attributes);
            if (command_obj.error) {
                err.stack = err.stack + command_obj.error.stack.replace(/^Error.*?\n/, '\n');
            }
            err.command = command_obj.command.toUpperCase();
            if (command_obj.args && command_obj.args.length) {
                err.args = command_obj.args;
            }
            if (options.error) {
                err.origin = options.error;
            }
            if (typeof command_obj.callback === 'function') {
                command_obj.callback(err);
            } else {
                aggregated_errors.push(err);
            }
        }
    }
    this.offline_queue = new Queue();
    while (this.command_queue.length > 0) {
        command_obj = this.command_queue.shift();
        if (typeof command_obj.callback === "function") {
            try {
                command_obj.callback(error);
            } catch (callback_err) {
                process.nextTick(function () {
                    throw callback_err;
                });
            }
    // Currently this would be a breaking change, therefore it's only emitted in debug_mode
    if (exports.debug_mode && aggregated_errors.length) {
        var error;
        if (aggregated_errors.length === 1) {
            error = aggregated_errors[0];
        } else {
            error_attributes.message = error_attributes.message.replace('It', 'They').replace(/command/i, '$&s');
            error = new errorClasses.AggregateError(error_attributes);
            error.errors = aggregated_errors;
        }
        this.emit('error', error);
    }
    this.command_queue = new Queue();
};
RedisClient.prototype.on_error = function (msg) {
    var message = "Redis connection to " + this.address + " failed - " + msg;
RedisClient.prototype.on_error = function (err) {
    if (this.closing) {
        return;
    }
    if (exports.debug_mode) {
        console.warn(message);
    }
    this.flush_and_error(message);
    err.message = 'Redis connection to ' + this.address + ' failed - ' + err.message;
    debug(err.message);
    this.connected = false;
    this.ready = false;
    this.emit("error", new Error(message));
    // "error" events get turned into exceptions if they aren't listened for.  If the user handled this error
    // Only emit the error if the retry_stategy option is not set
    if (!this.options.retry_strategy) {
        this.emit('error', err);
    }
    // 'error' events get turned into exceptions if they aren't listened for. If the user handled this error
    // then we should try to reconnect.
    this.connection_gone("error");
};
RedisClient.prototype.do_auth = function () {
    var self = this;
    if (exports.debug_mode) {
        console.log("Sending auth to " + self.address + " id " + self.connection_id);
    }
    self.send_anyway = true;
    self.send_command("auth", [this.auth_pass], function (err, res) {
        if (err) {
            if (err.toString().match("LOADING")) {
                // if redis is still loading the db, it will not authenticate and everything else will fail
                console.log("Redis still loading, trying to authenticate later");
                setTimeout(function () {
                    self.do_auth();
                }, 2000); // TODO - magic number alert
                return;
            } else if (err.toString().match("no password is set")) {
                console.log("Warning: Redis server does not require a password, but a password was supplied.")
                err = null;
                res = "OK";
            } else {
                return self.emit("error", new Error("Auth error: " + err.message));
            }
        }
        if (res.toString() !== "OK") {
            return self.emit("error", new Error("Auth failed: " + res.toString()));
        }
        if (exports.debug_mode) {
            console.log("Auth succeeded " + self.address + " id " + self.connection_id);
        }
        if (self.auth_callback) {
            self.auth_callback(err, res);
            self.auth_callback = null;
        }
        // now we are really connected
        self.emit("connect");
        self.initialize_retry_vars();
        if (self.options.no_ready_check) {
            self.on_ready();
        } else {
            self.ready_check();
        }
    });
    self.send_anyway = false;
    this.connection_gone('error', err);
};
RedisClient.prototype.on_connect = function () {
    if (exports.debug_mode) {
        console.log("Stream connected " + this.address + " id " + this.connection_id);
    }
    debug('Stream connected ' + this.address + ' id ' + this.connection_id);
    this.connected = true;
    this.ready = false;
    this.connections += 1;
    this.command_queue = new Queue();
    this.emitted_end = false;
    if (this.options.socket_nodelay) {
        this.stream.setNoDelay();
    }
    this.stream.setKeepAlive(this.options.socket_keepalive);
    this.stream.setTimeout(0);
    this.init_parser();
    this.emit('connect');
    this.initialize_retry_vars();
    if (this.auth_pass) {
        this.do_auth();
    if (this.options.no_ready_check) {
        this.on_ready();
    } else {
        this.emit("connect");
        this.initialize_retry_vars();
        if (this.options.no_ready_check) {
            this.on_ready();
        } else {
            this.ready_check();
        }
        this.ready_check();
    }
};
RedisClient.prototype.init_parser = function () {
RedisClient.prototype.on_ready = function () {
    var self = this;
    if (this.options.parser) {
        if (! parsers.some(function (parser) {
            if (parser.name === self.options.parser) {
                self.parser_module = parser;
                if (exports.debug_mode) {
                    console.log("Using parser module: " + self.parser_module.name);
                }
                return true;
            }
        })) {
            throw new Error("Couldn't find named parser " + self.options.parser + " on this system");
        }
    } else {
        if (exports.debug_mode) {
            console.log("Using default parser module: " + parsers[0].name);
        }
        this.parser_module = parsers[0];
    }
    this.parser_module.debug_mode = exports.debug_mode;
    // return_buffers sends back Buffers from parser to callback. detect_buffers sends back Buffers from parser, but
    // converts to Strings if the input arguments are not Buffers.
    this.reply_parser = new this.parser_module.Parser({
        return_buffers: self.options.return_buffers || self.options.detect_buffers || false
    });
    debug('on_ready called ' + this.address + ' id ' + this.connection_id);
    this.ready = true;
    // "reply error" is an error sent back by Redis
    this.reply_parser.on("reply error", function (reply) {
        if (reply instanceof Error) {
            self.return_error(reply);
    this.cork = function () {
        self.pipeline = true;
        if (self.stream.cork) {
            self.stream.cork();
        }
    };
    this.uncork = function () {
        if (self.fire_strings) {
            self.write_strings();
        } else {
            self.return_error(new Error(reply));
            self.write_buffers();
        }
    });
    this.reply_parser.on("reply", function (reply) {
        self.return_reply(reply);
    });
    // "error" is bad.  Somehow the parser got confused.  It'll try to reset and continue.
    this.reply_parser.on("error", function (err) {
        self.emit("error", new Error("Redis reply parser error: " + err.stack));
    });
};
RedisClient.prototype.on_ready = function () {
    var self = this;
    this.ready = true;
        self.pipeline = false;
        self.fire_strings = true;
        if (self.stream.uncork) {
            // TODO: Consider using next tick here. See https://github.com/NodeRedis/node_redis/issues/1033
            self.stream.uncork();
        }
    };
    if (this.old_state !== null) {
        this.monitoring = this.old_state.monitoring;
        this.pub_sub_mode = this.old_state.pub_sub_mode;
        this.selected_db = this.old_state.selected_db;
        this.old_state = null;
    }
    // magically restore any modal commands from a previous connection
    if (this.selected_db !== null) {
        // this trick works if and only if the following send_command
        // never goes into the offline queue
        var pub_sub_mode = this.pub_sub_mode;
        this.pub_sub_mode = false;
        this.send_command('select', [this.selected_db]);
        this.pub_sub_mode = pub_sub_mode;
    }
    if (this.pub_sub_mode === true) {
        // only emit "ready" when all subscriptions were made again
        var callback_count = 0;
    // Restore modal commands from previous connection. The order of the commands is important
    if (this.selected_db !== undefined) {
        this.internal_send_command(new Command('select', [this.selected_db]));
    }
    if (this.monitoring) { // Monitor has to be fired before pub sub commands
        this.internal_send_command(new Command('monitor', []));
    }
    var callback_count = Object.keys(this.subscription_set).length;
    if (!this.options.disable_resubscribing && callback_count) {
        // only emit 'ready' when all subscriptions were made again
        // TODO: Remove the countdown for ready here. This is not coherent with all other modes and should therefore not be handled special
        // We know we are ready as soon as all commands were fired
        var callback = function () {
            callback_count--;
            if (callback_count === 0) {
                self.emit("ready");
                self.emit('ready');
            }
        };
        Object.keys(this.subscription_set).forEach(function (key) {
            var parts = key.split(" ");
            if (exports.debug_mode) {
                console.warn("sending pub/sub on_ready " + parts[0] + ", " + parts[1]);
            }
            callback_count++;
            self.send_command(parts[0] + "scribe", [parts[1]], callback);
        });
        return;
    } else if (this.monitoring) {
        this.send_command("monitor");
    } else {
        debug('Sending pub/sub on_ready commands');
        for (var key in this.subscription_set) {
            var command = key.slice(0, key.indexOf('_'));
            var args = this.subscription_set[key];
            this[command]([args], callback);
        }
        this.send_offline_queue();
        return;
    }
    this.emit("ready");
    this.send_offline_queue();
    this.emit('ready');
};
RedisClient.prototype.on_info_cmd = function (err, res) {
    var self = this, obj = {}, lines, retry_time;
    if (err) {
        return self.emit("error", new Error("Ready check failed: " + err.message));
        if (err.message === "ERR unknown command 'info'") {
            this.on_ready();
            return;
        }
        err.message = 'Ready check failed: ' + err.message;
        this.emit('error', err);
        return;
    }
    lines = res.toString().split("\r\n");
    /* istanbul ignore if: some servers might not respond with any info data. This is just a safety check that is difficult to test */
    if (!res) {
        debug('The info command returned without any data.');
        this.on_ready();
        return;
    }
    lines.forEach(function (line) {
        var parts = line.split(':');
        if (parts[1]) {
            obj[parts[0]] = parts[1];
    if (!this.server_info.loading || this.server_info.loading === '0') {
        // If the master_link_status exists but the link is not up, try again after 50 ms
        if (this.server_info.master_link_status && this.server_info.master_link_status !== 'up') {
            this.server_info.loading_eta_seconds = 0.05;
        } else {
            // Eta loading should change
            debug('Redis server ready.');
            this.on_ready();
            return;
        }
    });
    obj.versions = [];
    if( obj.redis_version ){
        obj.redis_version.split('.').forEach(function (num) {
            obj.versions.push(+num);
        });
    }
    // expose info key/vals to users
    this.server_info = obj;
    if (!obj.loading || (obj.loading && obj.loading === "0")) {
        if (exports.debug_mode) {
            console.log("Redis server ready.");
        }
        this.on_ready();
    } else {
        retry_time = obj.loading_eta_seconds * 1000;
        if (retry_time > 1000) {
            retry_time = 1000;
        }
        if (exports.debug_mode) {
            console.log("Redis server still loading, trying again in " + retry_time);
        }
        setTimeout(function () {
            self.ready_check();
        }, retry_time);
    var retry_time = +this.server_info.loading_eta_seconds * 1000;
    if (retry_time > 1000) {
        retry_time = 1000;
    }
    debug('Redis server still loading, trying again in ' + retry_time);
    setTimeout(function (self) {
        self.ready_check();
    }, retry_time, this);
};
RedisClient.prototype.ready_check = function () {
    var self = this;
    if (exports.debug_mode) {
        console.log("checking server ready state...");
    }
    this.send_anyway = true;  // secret flag to send_command to send something even if not "ready"
    debug('Checking server ready state...');
    // Always fire this info command as first command even if other commands are already queued up
    this.ready = true;
    this.info(function (err, res) {
        self.on_info_cmd(err, res);
    });
    this.send_anyway = false;
    this.ready = false;
};
RedisClient.prototype.send_offline_queue = function () {
    var command_obj, buffered_writes = 0;
    while (this.offline_queue.length > 0) {
        command_obj = this.offline_queue.shift();
        if (exports.debug_mode) {
            console.log("Sending offline command: " + command_obj.command);
        }
        buffered_writes += !this.send_command(command_obj.command, command_obj.args, command_obj.callback);
    for (var command_obj = this.offline_queue.shift(); command_obj; command_obj = this.offline_queue.shift()) {
        debug('Sending offline command: ' + command_obj.command);
        this.internal_send_command(command_obj);
    }
    this.offline_queue = new Queue();
    // Even though items were shifted off, Queue backing store still uses memory until next add, so just get a new Queue
    this.drain();
};
var retry_connection = function (self, error) {
    debug('Retrying connection...');
    if (!buffered_writes) {
        this.should_buffer = false;
        this.emit("drain");
    var reconnect_params = {
        delay: self.retry_delay,
        attempt: self.attempts,
        error: error
    };
    if (self.options.camel_case) {
        reconnect_params.totalRetryTime = self.retry_totaltime;
        reconnect_params.timesConnected = self.times_connected;
    } else {
        reconnect_params.total_retry_time = self.retry_totaltime;
        reconnect_params.times_connected = self.times_connected;
    }
};
    self.emit('reconnecting', reconnect_params);
RedisClient.prototype.connection_gone = function (why) {
    var self = this;
    self.retry_totaltime += self.retry_delay;
    self.attempts += 1;
    self.retry_delay = Math.round(self.retry_delay * self.retry_backoff);
    self.create_stream();
    self.retry_timer = null;
};
RedisClient.prototype.connection_gone = function (why, error) {
    // If a retry is already in progress, just let that happen
    if (this.retry_timer) {
        return;
    }
    error = error || null;
    if (exports.debug_mode) {
        console.warn("Redis connection is gone from " + why + " event.");
    }
    debug('Redis connection is gone from ' + why + ' event.');
    this.connected = false;
    this.ready = false;
    if (this.old_state === null) {
        var state = {
            monitoring: this.monitoring,
            pub_sub_mode: this.pub_sub_mode,
            selected_db: this.selected_db
        };
        this.old_state = state;
        this.monitoring = false;
        this.pub_sub_mode = false;
        this.selected_db = null;
    }
    // Deactivate cork to work with the offline queue
    this.cork = noop;
    this.uncork = noop;
    this.pipeline = false;
    this.pub_sub_mode = 0;
    // since we are collapsing end and close, users don't expect to be called twice
    if (! this.emitted_end) {
        this.emit("end");
    if (!this.emitted_end) {
        this.emit('end');
        this.emitted_end = true;
    }
    this.flush_and_error("Redis connection gone from " + why + " event.");
    // If this is a requested shutdown, then don't retry
    if (this.closing) {
        this.retry_timer = null;
        if (exports.debug_mode) {
            console.warn("connection ended from quit command, not retrying.");
        }
        return;
    }
    var nextDelay = Math.floor(this.retry_delay * this.retry_backoff);
    if (this.retry_max_delay !== null && nextDelay > this.retry_max_delay) {
        this.retry_delay = this.retry_max_delay;
    } else {
        this.retry_delay = nextDelay;
    }
    if (exports.debug_mode) {
        console.log("Retry connection in " + this.retry_delay + " ms");
    }
    if (this.max_attempts && this.attempts >= this.max_attempts) {
        this.retry_timer = null;
        // TODO - some people need a "Redis is Broken mode" for future commands that errors immediately, and others
        // want the program to exit.  Right now, we just log, which doesn't really help in either case.
        console.error("node_redis: Couldn't get Redis connection after " + this.max_attempts + " attempts.");
        debug('Connection ended by quit / end command, not retrying.');
        this.flush_and_error({
            message: 'Stream connection ended and command aborted.',
            code: 'NR_CLOSED'
        }, {
            error: error
        });
        return;
    }
    this.attempts += 1;
    this.emit("reconnecting", {
        delay: self.retry_delay,
        attempt: self.attempts
    });
    this.retry_timer = setTimeout(function () {
        if (exports.debug_mode) {
            console.log("Retrying connection...");
    if (typeof this.options.retry_strategy === 'function') {
        var retry_params = {
            attempt: this.attempts,
            error: error
        };
        if (this.options.camel_case) {
            retry_params.totalRetryTime = this.retry_totaltime;
            retry_params.timesConnected = this.times_connected;
        } else {
            retry_params.total_retry_time = this.retry_totaltime;
            retry_params.times_connected = this.times_connected;
        }
        self.retry_totaltime += self.retry_delay;
        if (self.connect_timeout && self.retry_totaltime >= self.connect_timeout) {
            self.retry_timer = null;
            // TODO - engage Redis is Broken mode for future commands, or whatever
            console.error("node_redis: Couldn't get Redis connection after " + self.retry_totaltime + "ms.");
        this.retry_delay = this.options.retry_strategy(retry_params);
        if (typeof this.retry_delay !== 'number') {
            // Pass individual error through
            if (this.retry_delay instanceof Error) {
                error = this.retry_delay;
            }
            this.flush_and_error({
                message: 'Stream connection ended and command aborted.',
                code: 'NR_CLOSED'
            }, {
                error: error
            });
            this.end(false);
            return;
        }
        self.stream = net.createConnection(self.connectionOption);
        self.install_stream_listeners();
        self.retry_timer = null;
    }, this.retry_delay);
};
RedisClient.prototype.on_data = function (data) {
    if (exports.debug_mode) {
        console.log("net read " + this.address + " id " + this.connection_id + ": " + data.toString());
    }
    try {
        this.reply_parser.execute(data);
    } catch (err) {
        // This is an unexpected parser problem, an exception that came from the parser code itself.
        // Parser should emit "error" events if it notices things are out of whack.
        // Callbacks that throw exceptions will land in return_reply(), below.
        // TODO - it might be nice to have a different "error" event for different types of errors
        this.emit("error", err);
    }
};
RedisClient.prototype.return_error = function (err) {
    var command_obj = this.command_queue.shift(), queue_len = this.command_queue.getLength();
    if (this.max_attempts !== 0 && this.attempts >= this.max_attempts || this.retry_totaltime >= this.connect_timeout) {
        var message = 'Redis connection in broken state: ';
        if (this.retry_totaltime >= this.connect_timeout) {
            message += 'connection timeout exceeded.';
        } else {
            message += 'maximum connection attempts exceeded.';
        }
    if (this.pub_sub_mode === false && queue_len === 0) {
        this.command_queue = new Queue();
        this.emit("idle");
    }
    if (this.should_buffer && queue_len <= this.command_queue_low_water) {
        this.emit("drain");
        this.should_buffer = false;
        this.flush_and_error({
            message: message,
            code: 'CONNECTION_BROKEN',
        }, {
            error: error
        });
        var err = new Error(message);
        err.code = 'CONNECTION_BROKEN';
        if (error) {
            err.origin = error;
        }
        this.emit('error', err);
        this.end(false);
        return;
    }
    if (command_obj && typeof command_obj.callback === "function") {
        try {
            command_obj.callback(err);
        } catch (callback_err) {
            // if a callback throws an exception, re-throw it on a new stack so the parser can keep going
            process.nextTick(function () {
                throw callback_err;
            });
        }
    } else {
        console.log("node_redis: no callback to send error: " + err.message);
        // this will probably not make it anywhere useful, but we might as well throw
        process.nextTick(function () {
            throw err;
    // Retry commands after a reconnect instead of throwing an error. Use this with caution
    if (this.options.retry_unfulfilled_commands) {
        this.offline_queue.unshift.apply(this.offline_queue, this.command_queue.toArray());
        this.command_queue.clear();
    } else if (this.command_queue.length !== 0) {
        this.flush_and_error({
            message: 'Redis connection lost and command aborted.',
            code: 'UNCERTAIN_STATE'
        }, {
            error: error,
            queues: ['command_queue']
        });
    }
};
// if a callback throws an exception, re-throw it on a new stack so the parser can keep going.
// if a domain is active, emit the error on the domain, which will serve the same function.
// put this try/catch in its own function because V8 doesn't optimize this well yet.
function try_callback(callback, reply) {
    try {
        callback(null, reply);
    } catch (err) {
        if (process.domain) {
            var currDomain = process.domain;
            currDomain.emit('error', err);
            if (process.domain === currDomain) {
                currDomain.exit();
            }
        } else {
            process.nextTick(function () {
                throw err;
            });
        }
    if (this.retry_max_delay !== null && this.retry_delay > this.retry_max_delay) {
        this.retry_delay = this.retry_max_delay;
    } else if (this.retry_totaltime + this.retry_delay > this.connect_timeout) {
        // Do not exceed the maximum
        this.retry_delay = this.connect_timeout - this.retry_totaltime;
    }
}
// hgetall converts its replies to an Object.  If the reply is empty, null is returned.
function reply_to_object(reply) {
    var obj = {}, j, jl, key, val;
    debug('Retry connection in ' + this.retry_delay + ' ms');
    if (reply.length === 0) {
        return null;
    }
    this.retry_timer = setTimeout(retry_connection, this.retry_delay, this, error);
};
    for (j = 0, jl = reply.length; j < jl; j += 2) {
        key = reply[j].toString('binary');
        val = reply[j + 1];
        obj[key] = val;
RedisClient.prototype.return_error = function (err) {
    var command_obj = this.command_queue.shift();
    if (command_obj.error) {
        err.stack = command_obj.error.stack.replace(/^Error.*?\n/, 'ReplyError: ' + err.message + '\n');
    }
    err.command = command_obj.command.toUpperCase();
    if (command_obj.args && command_obj.args.length) {
        err.args = command_obj.args;
    }
    return obj;
}
function reply_to_strings(reply) {
    var i;
    if (Buffer.isBuffer(reply)) {
        return reply.toString();
    // Count down pub sub mode if in entering modus
    if (this.pub_sub_mode > 1) {
        this.pub_sub_mode--;
    }
    if (Array.isArray(reply)) {
        for (i = 0; i < reply.length; i++) {
            if (reply[i] !== null && reply[i] !== undefined) {
                reply[i] = reply[i].toString();
            }
        }
        return reply;
    var match = err.message.match(utils.err_code);
    // LUA script could return user errors that don't behave like all other errors!
    if (match) {
        err.code = match[1];
    }
    return reply;
}
    utils.callback_or_emit(this, command_obj.callback, err);
};
RedisClient.prototype.return_reply = function (reply) {
    var command_obj, len, type, timestamp, argindex, args, queue_len;
RedisClient.prototype.drain = function () {
    this.emit('drain');
    this.should_buffer = false;
};
    // If the "reply" here is actually a message received asynchronously due to a
    // pubsub subscription, don't pop the command queue as we'll only be consuming
    // the head command prematurely.
    if (Array.isArray(reply) && reply.length > 0 && reply[0]) {
        type = reply[0].toString();
RedisClient.prototype.emit_idle = function () {
    if (this.command_queue.length === 0 && this.pub_sub_mode === 0) {
        this.emit('idle');
    }
};
    if (this.pub_sub_mode && (type == 'message' || type == 'pmessage')) {
        trace("received pubsub message");
    }
    else {
        command_obj = this.command_queue.shift();
function normal_reply (self, reply) {
    var command_obj = self.command_queue.shift();
    if (typeof command_obj.callback === 'function') {
        if (command_obj.command !== 'exec') {
            reply = self.handle_reply(reply, command_obj.command, command_obj.buffer_args);
        }
        command_obj.callback(null, reply);
    } else {
        debug('No callback for reply');
    }
}
    queue_len = this.command_queue.getLength();
    if (this.pub_sub_mode === false && queue_len === 0) {
        this.command_queue = new Queue();  // explicitly reclaim storage from old Queue
        this.emit("idle");
    }
    if (this.should_buffer && queue_len <= this.command_queue_low_water) {
        this.emit("drain");
        this.should_buffer = false;
function subscribe_unsubscribe (self, reply, type) {
    // Subscribe commands take an optional callback and also emit an event, but only the _last_ response is included in the callback
    // The pub sub commands return each argument in a separate return value and have to be handled that way
    var command_obj = self.command_queue.get(0);
    var buffer = self.options.return_buffers || self.options.detect_buffers && command_obj.buffer_args;
    var channel = (buffer || reply[1] === null) ? reply[1] : reply[1].toString();
    var count = +reply[2]; // Return the channel counter as number no matter if `string_numbers` is activated or not
    debug(type, channel);
    // Emit first, then return the callback
    if (channel !== null) { // Do not emit or "unsubscribe" something if there was no channel to unsubscribe from
        self.emit(type, channel, count);
        if (type === 'subscribe' || type === 'psubscribe') {
            self.subscription_set[type + '_' + channel] = channel;
        } else {
            type = type === 'unsubscribe' ? 'subscribe' : 'psubscribe'; // Make types consistent
            delete self.subscription_set[type + '_' + channel];
        }
    }
    if (command_obj && !command_obj.sub_command) {
        if (typeof command_obj.callback === "function") {
            if (this.options.detect_buffers && command_obj.buffer_args === false) {
                // If detect_buffers option was specified, then the reply from the parser will be Buffers.
                // If this command did not use Buffer arguments, then convert the reply to Strings here.
                reply = reply_to_strings(reply);
            }
            // TODO - confusing and error-prone that hgetall is special cased in two places
            if (reply && 'hgetall' === command_obj.command.toLowerCase()) {
                reply = reply_to_object(reply);
            }
            try_callback(command_obj.callback, reply);
        } else if (exports.debug_mode) {
            console.log("no callback for reply: " + (reply && reply.toString && reply.toString()));
        }
    } else if (this.pub_sub_mode || (command_obj && command_obj.sub_command)) {
        if (Array.isArray(reply)) {
            type = reply[0].toString();
            if (type === "message") {
                this.emit("message", reply[1].toString(), reply[2]); // channel, message
            } else if (type === "pmessage") {
                this.emit("pmessage", reply[1].toString(), reply[2].toString(), reply[3]); // pattern, channel, message
            } else if (type === "subscribe" || type === "unsubscribe" || type === "psubscribe" || type === "punsubscribe") {
                if (reply[2] === 0) {
                    this.pub_sub_mode = false;
                    if (this.debug_mode) {
                        console.log("All subscriptions removed, exiting pub/sub mode");
                    }
                } else {
                    this.pub_sub_mode = true;
                }
                // subscribe commands take an optional callback and also emit an event, but only the first response is included in the callback
                // TODO - document this or fix it so it works in a more obvious way
                // reply[1] can be null
                var reply1String = (reply[1] === null) ? null : reply[1].toString();
                if (command_obj && typeof command_obj.callback === "function") {
                    try_callback(command_obj.callback, reply1String);
    if (command_obj.args.length === 1 || self.sub_commands_left === 1 || command_obj.args.length === 0 && (count === 0 || channel === null)) {
        if (count === 0) { // unsubscribed from all channels
            var running_command;
            var i = 1;
            self.pub_sub_mode = 0; // Deactivating pub sub mode
            // This should be a rare case and therefore handling it this way should be good performance wise for the general case
            while (running_command = self.command_queue.get(i)) {
                if (SUBSCRIBE_COMMANDS[running_command.command]) {
                    self.pub_sub_mode = i; // Entering pub sub mode again
                    break;
                }
                this.emit(type, reply1String, reply[2]); // channel, count
            } else {
                throw new Error("subscriptions are active but got unknown reply type " + type);
                i++;
            }
        } else if (! this.closing) {
            throw new Error("subscriptions are active but got an invalid reply: " + reply);
        }
    } else if (this.monitoring) {
        len = reply.indexOf(" ");
        timestamp = reply.slice(0, len);
        argindex = reply.indexOf('"');
        args = reply.slice(argindex + 1, -1).split('" "').map(function (elem) {
            return elem.replace(/\\"/g, '"');
        });
        this.emit("monitor", timestamp, args);
        self.command_queue.shift();
        if (typeof command_obj.callback === 'function') {
            // TODO: The current return value is pretty useless.
            // Evaluate to change this in v.3 to return all subscribed / unsubscribed channels in an array including the number of channels subscribed too
            command_obj.callback(null, channel);
        }
        self.sub_commands_left = 0;
    } else {
        throw new Error("node_redis command queue state error. If you can reproduce this, please report it.");
        if (self.sub_commands_left !== 0) {
            self.sub_commands_left--;
        } else {
            self.sub_commands_left = command_obj.args.length ? command_obj.args.length - 1 : count;
        }
    }
};
// This Command constructor is ever so slightly faster than using an object literal, but more importantly, using
// a named constructor helps it show up meaningfully in the V8 CPU profiler and in heap snapshots.
function Command(command, args, sub_command, buffer_args, callback) {
    this.command = command;
    this.args = args;
    this.sub_command = sub_command;
    this.buffer_args = buffer_args;
    this.callback = callback;
}
RedisClient.prototype.send_command = function (command, args, callback) {
    var arg, command_obj, i, il, elem_count, buffer_args, stream = this.stream, command_str = "", buffered_writes = 0, last_arg_type, lcaseCommand;
    if (typeof command !== "string") {
        throw new Error("First argument to send_command must be the command name string, not " + typeof command);
    }
    if (Array.isArray(args)) {
        if (typeof callback === "function") {
            // probably the fastest way:
            //     client.command([arg1, arg2], cb);  (straight passthrough)
            //         send_command(command, [arg1, arg2], cb);
        } else if (! callback) {
            // most people find this variable argument length form more convenient, but it uses arguments, which is slower
            //     client.command(arg1, arg2, cb);   (wraps up arguments into an array)
            //       send_command(command, [arg1, arg2, cb]);
            //     client.command(arg1, arg2);   (callback is optional)
            //       send_command(command, [arg1, arg2]);
            //     client.command(arg1, arg2, undefined);   (callback is undefined)
            //       send_command(command, [arg1, arg2, undefined]);
            last_arg_type = typeof args[args.length - 1];
            if (last_arg_type === "function" || last_arg_type === "undefined") {
                callback = args.pop();
            }
function return_pub_sub (self, reply) {
    var type = reply[0].toString();
    if (type === 'message') { // channel, message
        if (!self.options.return_buffers || self.message_buffers) { // backwards compatible. Refactor this in v.3 to always return a string on the normal emitter
            self.emit('message', reply[1].toString(), reply[2].toString());
            self.emit('message_buffer', reply[1], reply[2]);
            self.emit('messageBuffer', reply[1], reply[2]);
        } else {
            self.emit('message', reply[1], reply[2]);
        }
    } else if (type === 'pmessage') { // pattern, channel, message
        if (!self.options.return_buffers || self.message_buffers) { // backwards compatible. Refactor this in v.3 to always return a string on the normal emitter
            self.emit('pmessage', reply[1].toString(), reply[2].toString(), reply[3].toString());
            self.emit('pmessage_buffer', reply[1], reply[2], reply[3]);
            self.emit('pmessageBuffer', reply[1], reply[2], reply[3]);
        } else {
            throw new Error("send_command: last argument must be a callback or undefined");
            self.emit('pmessage', reply[1], reply[2], reply[3]);
        }
    } else {
        throw new Error("send_command: second argument must be an array");
    }
    if (callback && process.domain) callback = process.domain.bind(callback);
    // if the last argument is an array and command is sadd or srem, expand it out:
    //     client.sadd(arg1, [arg2, arg3, arg4], cb);
    //  converts to:
    //     client.sadd(arg1, arg2, arg3, arg4, cb);
    lcaseCommand = command.toLowerCase();
    if ((lcaseCommand === 'sadd' || lcaseCommand === 'srem') && args.length > 0 && Array.isArray(args[args.length - 1])) {
        args = args.slice(0, -1).concat(args[args.length - 1]);
        subscribe_unsubscribe(self, reply, type);
    }
}
    // if the value is undefined or null and command is set or setx, need not to send message to redis
    if (command === 'set' || command === 'setex') {
        if(args[args.length - 1] === undefined || args[args.length - 1] === null) {
            var err = new Error('send_command: ' + command + ' value must not be undefined or null');
            return callback && callback(err);
RedisClient.prototype.return_reply = function (reply) {
    // If in monitor mode, all normal commands are still working and we only want to emit the streamlined commands
    // As this is not the average use case and monitor is expensive anyway, let's change the code here, to improve
    // the average performance of all other commands in case of no monitor mode
    if (this.monitoring) {
        var replyStr;
        if (this.buffers && Buffer.isBuffer(reply)) {
            replyStr = reply.toString();
        } else {
            replyStr = reply;
        }
    }
    buffer_args = false;
    for (i = 0, il = args.length, arg; i < il; i += 1) {
        if (Buffer.isBuffer(args[i])) {
            buffer_args = true;
        // While reconnecting the redis server does not recognize the client as in monitor mode anymore
        // Therefore the monitor command has to finish before it catches further commands
        if (typeof replyStr === 'string' && utils.monitor_regex.test(replyStr)) {
            var timestamp = replyStr.slice(0, replyStr.indexOf(' '));
            var args = replyStr.slice(replyStr.indexOf('"') + 1, -1).split('" "').map(function (elem) {
                return elem.replace(/\\"/g, '"');
            });
            this.emit('monitor', timestamp, args, replyStr);
            return;
        }
    }
    if (this.pub_sub_mode === 0) {
        normal_reply(this, reply);
    } else if (this.pub_sub_mode !== 1) {
        this.pub_sub_mode--;
        normal_reply(this, reply);
    } else if (!(reply instanceof Array) || reply.length <= 2) {
        // Only PING and QUIT are allowed in this context besides the pub sub commands
        // Ping replies with ['pong', null|value] and quit with 'OK'
        normal_reply(this, reply);
    } else {
        return_pub_sub(this, reply);
    }
};
    command_obj = new Command(command, args, false, buffer_args, callback);
    if ((!this.ready && !this.send_anyway) || !stream.writable) {
        if (exports.debug_mode) {
            if (!stream.writable) {
                console.log("send command: stream is not writeable.");
function handle_offline_command (self, command_obj) {
    var command = command_obj.command;
    var err, msg;
    if (self.closing || !self.enable_offline_queue) {
        command = command.toUpperCase();
        if (!self.closing) {
            if (self.stream.writable) {
                msg = 'The connection is not yet established and the offline queue is deactivated.';
            } else {
                msg = 'Stream not writeable.';
            }
        } else {
            msg = 'The connection is already closed.';
        }
        err = new errorClasses.AbortError({
            message: command + " can't be processed. " + msg,
            code: 'NR_CLOSED',
            command: command
        });
        if (command_obj.args.length) {
            err.args = command_obj.args;
        }
        utils.reply_in_order(self, command_obj.callback, err);
    } else {
        debug('Queueing ' + command + ' for next server connection.');
        self.offline_queue.push(command_obj);
    }
    self.should_buffer = true;
}
        if (this.enable_offline_queue) {
            if (exports.debug_mode) {
                console.log("Queueing " + command + " for next server connection.");
// Do not call internal_send_command directly, if you are not absolutly certain it handles everything properly
// e.g. monitor / info does not work with internal_send_command only
RedisClient.prototype.internal_send_command = function (command_obj) {
    var arg, prefix_keys;
    var i = 0;
    var command_str = '';
    var args = command_obj.args;
    var command = command_obj.command;
    var len = args.length;
    var big_data = false;
    var args_copy = new Array(len);
    if (process.domain && command_obj.callback) {
        command_obj.callback = process.domain.bind(command_obj.callback);
    }
    if (this.ready === false || this.stream.writable === false) {
        // Handle offline commands right away
        handle_offline_command(this, command_obj);
        return false; // Indicate buffering
    }
    for (i = 0; i < len; i += 1) {
        if (typeof args[i] === 'string') {
            // 30000 seemed to be a good value to switch to buffers after testing and checking the pros and cons
            if (args[i].length > 30000) {
                big_data = true;
                args_copy[i] = new Buffer(args[i], 'utf8');
            } else {
                args_copy[i] = args[i];
            }
            this.offline_queue.push(command_obj);
            this.should_buffer = true;
        } else {
            var not_writeable_error = new Error('send_command: stream not writeable. enable_offline_queue is false');
            if (command_obj.callback) {
                command_obj.callback(not_writeable_error);
        } else if (typeof args[i] === 'object') { // Checking for object instead of Buffer.isBuffer helps us finding data types that we can't handle properly
            if (args[i] instanceof Date) { // Accept dates as valid input
                args_copy[i] = args[i].toString();
            } else if (args[i] === null) {
                this.warn(
                    'Deprecated: The ' + command.toUpperCase() + ' command contains a "null" argument.\n' +
                    'This is converted to a "null" string now and will return an error from v.3.0 on.\n' +
                    'Please handle this in your code to make sure everything works as you intended it to.'
                );
                args_copy[i] = 'null'; // Backwards compatible :/
            } else if (Buffer.isBuffer(args[i])) {
                args_copy[i] = args[i];
                command_obj.buffer_args = true;
                big_data = true;
            } else {
                throw not_writeable_error;
                this.warn(
                    'Deprecated: The ' + command.toUpperCase() + ' command contains a argument of type ' + args[i].constructor.name + '.\n' +
                    'This is converted to "' + args[i].toString() + '" by using .toString() now and will return an error from v.3.0 on.\n' +
                    'Please handle this in your code to make sure everything works as you intended it to.'
                );
                args_copy[i] = args[i].toString(); // Backwards compatible :/
            }
        } else if (typeof args[i] === 'undefined') {
            this.warn(
                'Deprecated: The ' + command.toUpperCase() + ' command contains a "undefined" argument.\n' +
                'This is converted to a "undefined" string now and will return an error from v.3.0 on.\n' +
                'Please handle this in your code to make sure everything works as you intended it to.'
            );
            args_copy[i] = 'undefined'; // Backwards compatible :/
        } else {
            // Seems like numbers are converted fast using string concatenation
            args_copy[i] = '' + args[i];
        }
        return false;
    }
    if (command === "subscribe" || command === "psubscribe" || command === "unsubscribe" || command === "punsubscribe") {
        this.pub_sub_command(command_obj);
    } else if (command === "monitor") {
        this.monitoring = true;
    } else if (command === "quit") {
        this.closing = true;
    } else if (this.pub_sub_mode === true) {
        throw new Error("Connection in subscriber mode, only subscriber commands may be used");
    if (this.options.prefix) {
        prefix_keys = commands.getKeyIndexes(command, args_copy);
        for (i = prefix_keys.pop(); i !== undefined; i = prefix_keys.pop()) {
            args_copy[i] = this.options.prefix + args_copy[i];
        }
    }
    this.command_queue.push(command_obj);
    this.commands_sent += 1;
    elem_count = args.length + 1;
    // Always use "Multi bulk commands", but if passed any Buffer args, then do multiple writes, one for each arg.
    if (typeof this.options.rename_commands !== 'undefined' && this.options.rename_commands[command]) {
        command = this.options.rename_commands[command];
    }
    // Always use 'Multi bulk commands', but if passed any Buffer args, then do multiple writes, one for each arg.
    // This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer.
    command_str = '*' + (len + 1) + '\r\n$' + command.length + '\r\n' + command + '\r\n';
    command_str = "*" + elem_count + "\r\n$" + command.length + "\r\n" + command + "\r\n";
    if (! buffer_args) { // Build up a string and send entire command in one write
        for (i = 0, il = args.length, arg; i < il; i += 1) {
            arg = args[i];
            if (typeof arg !== "string") {
                arg = String(arg);
            }
            command_str += "$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n";
        }
        if (exports.debug_mode) {
            console.log("send " + this.address + " id " + this.connection_id + ": " + command_str);
    if (big_data === false) { // Build up a string and send entire command in one write
        for (i = 0; i < len; i += 1) {
            arg = args_copy[i];
            command_str += '$' + Buffer.byteLength(arg) + '\r\n' + arg + '\r\n';
        }
        buffered_writes += !stream.write(command_str);
        debug('Send ' + this.address + ' id ' + this.connection_id + ': ' + command_str);
        this.write(command_str);
    } else {
        if (exports.debug_mode) {
            console.log("send command (" + command_str + ") has Buffer arguments");
        }
        buffered_writes += !stream.write(command_str);
        for (i = 0, il = args.length, arg; i < il; i += 1) {
            arg = args[i];
            if (!(Buffer.isBuffer(arg) || arg instanceof String)) {
                arg = String(arg);
            }
            if (Buffer.isBuffer(arg)) {
                if (arg.length === 0) {
                    if (exports.debug_mode) {
                        console.log("send_command: using empty string for 0 length buffer");
                    }
                    buffered_writes += !stream.write("$0\r\n\r\n");
                } else {
                    buffered_writes += !stream.write("$" + arg.length + "\r\n");
                    buffered_writes += !stream.write(arg);
                    buffered_writes += !stream.write("\r\n");
                    if (exports.debug_mode) {
                        console.log("send_command: buffer send " + arg.length + " bytes");
                    }
                }
            } else {
                if (exports.debug_mode) {
                    console.log("send_command: string send " + Buffer.byteLength(arg) + " bytes: " + arg);
                }
                buffered_writes += !stream.write("$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n");
        debug('Send command (' + command_str + ') has Buffer arguments');
        this.fire_strings = false;
        this.write(command_str);
        for (i = 0; i < len; i += 1) {
            arg = args_copy[i];
            if (typeof arg === 'string') {
                this.write('$' + Buffer.byteLength(arg) + '\r\n' + arg + '\r\n');
            } else { // buffer
                this.write('$' + arg.length + '\r\n');
                this.write(arg);
                this.write('\r\n');
            }
            debug('send_command: buffer send ' + arg.length + ' bytes');
        }
    }
    if (exports.debug_mode) {
        console.log("send_command buffered_writes: " + buffered_writes, " should_buffer: " + this.should_buffer);
    if (command_obj.call_on_write) {
        command_obj.call_on_write();
    }
    if (buffered_writes || this.command_queue.getLength() >= this.command_queue_high_water) {
        this.should_buffer = true;
    // Handle `CLIENT REPLY ON|OFF|SKIP`
    // This has to be checked after call_on_write
    /* istanbul ignore else: TODO: Remove this as soon as we test Redis 3.2 on travis */
    if (this.reply === 'ON') {
        this.command_queue.push(command_obj);
    } else {
        // Do not expect a reply
        // Does this work in combination with the pub sub mode?
        if (command_obj.callback) {
            utils.reply_in_order(this, command_obj.callback, null, undefined, this.command_queue);
        }
        if (this.reply === 'SKIP') {
            this.reply = 'SKIP_ONE_MORE';
        } else if (this.reply === 'SKIP_ONE_MORE') {
            this.reply = 'ON';
        }
    }
    return !this.should_buffer;
};
RedisClient.prototype.pub_sub_command = function (command_obj) {
    var i, key, command, args;
    if (this.pub_sub_mode === false && exports.debug_mode) {
        console.log("Entering pub/sub mode from " + command_obj.command);
    }
    this.pub_sub_mode = true;
    command_obj.sub_command = true;
    command = command_obj.command;
    args = command_obj.args;
    if (command === "subscribe" || command === "psubscribe") {
        if (command === "subscribe") {
            key = "sub";
        } else {
            key = "psub";
        }
        for (i = 0; i < args.length; i++) {
            this.subscription_set[key + " " + args[i]] = true;
        }
    } else {
        if (command === "unsubscribe") {
            key = "sub";
        } else {
            key = "psub";
        }
        for (i = 0; i < args.length; i++) {
            delete this.subscription_set[key + " " + args[i]];
RedisClient.prototype.write_strings = function () {
    var str = '';
    for (var command = this.pipeline_queue.shift(); command; command = this.pipeline_queue.shift()) {
        // Write to stream if the string is bigger than 4mb. The biggest string may be Math.pow(2, 28) - 15 chars long
        if (str.length + command.length > 4 * 1024 * 1024) {
            this.should_buffer = !this.stream.write(str);
            str = '';
        }
        str += command;
    }
    if (str !== '') {
        this.should_buffer = !this.stream.write(str);
    }
};
RedisClient.prototype.end = function () {
    this.stream._events = {};
    //clear retry_timer
    if(this.retry_timer){
        clearTimeout(this.retry_timer);
        this.retry_timer=null;
RedisClient.prototype.write_buffers = function () {
    for (var command = this.pipeline_queue.shift(); command; command = this.pipeline_queue.shift()) {
        this.should_buffer = !this.stream.write(command);
    }
    this.stream.on("error", function(){});
    this.connected = false;
    this.ready = false;
    this.closing = true;
    return this.stream.destroySoon();
};
function Multi(client, args) {
    this._client = client;
    this.queue = [["MULTI"]];
    if (Array.isArray(args)) {
        this.queue = this.queue.concat(args);
RedisClient.prototype.write = function (data) {
    if (this.pipeline === false) {
        this.should_buffer = !this.stream.write(data);
        return;
    }
}
exports.Multi = Multi;
// take 2 arrays and return the union of their elements
function set_union(seta, setb) {
    var obj = {};
    seta.forEach(function (val) {
        obj[val] = true;
    });
    setb.forEach(function (val) {
        obj[val] = true;
    });
    return Object.keys(obj);
}
// This static list of commands is updated from time to time.  ./lib/commands.js can be updated with generate_commands.js
commands = set_union(["get", "set", "setnx", "setex", "append", "strlen", "del", "exists", "setbit", "getbit", "setrange", "getrange", "substr",
    "incr", "decr", "mget", "rpush", "lpush", "rpushx", "lpushx", "linsert", "rpop", "lpop", "brpop", "brpoplpush", "blpop", "llen", "lindex",
    "lset", "lrange", "ltrim", "lrem", "rpoplpush", "sadd", "srem", "smove", "sismember", "scard", "spop", "srandmember", "sinter", "sinterstore",
    "sunion", "sunionstore", "sdiff", "sdiffstore", "smembers", "zadd", "zincrby", "zrem", "zremrangebyscore", "zremrangebyrank", "zunionstore",
    "zinterstore", "zrange", "zrangebyscore", "zrevrangebyscore", "zcount", "zrevrange", "zcard", "zscore", "zrank", "zrevrank", "hset", "hsetnx",
    "hget", "hmset", "hmget", "hincrby", "hdel", "hlen", "hkeys", "hvals", "hgetall", "hexists", "incrby", "decrby", "getset", "mset", "msetnx",
    "randomkey", "select", "move", "rename", "renamenx", "expire", "expireat", "keys", "dbsize", "auth", "ping", "echo", "save", "bgsave",
    "bgrewriteaof", "shutdown", "lastsave", "type", "multi", "exec", "discard", "sync", "flushdb", "flushall", "sort", "info", "monitor", "ttl",
    "persist", "slaveof", "debug", "config", "subscribe", "unsubscribe", "psubscribe", "punsubscribe", "publish", "watch", "unwatch", "cluster",
    "restore", "migrate", "dump", "object", "client", "eval", "evalsha"], require("./lib/commands"));
commands.forEach(function (fullCommand) {
    var command = fullCommand.split(' ')[0];
    RedisClient.prototype[command] = function (args, callback) {
        if (Array.isArray(args) && typeof callback === "function") {
            return this.send_command(command, args, callback);
        } else {
            return this.send_command(command, to_array(arguments));
        }
    };
    RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command];
    Multi.prototype[command] = function () {
        this.queue.push([command].concat(to_array(arguments)));
        return this;
    };
    Multi.prototype[command.toUpperCase()] = Multi.prototype[command];
});
// store db in this.select_db to restore it on reconnect
RedisClient.prototype.select = function (db, callback) {
    var self = this;
    this.send_command('select', [db], function (err, res) {
        if (err === null) {
            self.selected_db = db;
        }
        if (typeof(callback) === 'function') {
            callback(err, res);
        } else if (err) {
            self.emit('error', err);
        }
    });
    this.pipeline_queue.push(data);
};
RedisClient.prototype.SELECT = RedisClient.prototype.select;
// Stash auth for connect and reconnect.  Send immediately if already connected.
RedisClient.prototype.auth = function () {
    var args = to_array(arguments);
    this.auth_pass = args[0];
    this.auth_callback = args[1];
    if (exports.debug_mode) {
        console.log("Saving auth as " + this.auth_pass);
Object.defineProperty(exports, 'debugMode', {
    get: function () {
        return this.debug_mode;
    },
    set: function (val) {
        this.debug_mode = val;
    }
});
    if (this.connected) {
        this.send_command("auth", args);
// Don't officially expose the command_queue directly but only the length as read only variable
Object.defineProperty(RedisClient.prototype, 'command_queue_length', {
    get: function () {
        return this.command_queue.length;
    }
};
RedisClient.prototype.AUTH = RedisClient.prototype.auth;
});
RedisClient.prototype.hmget = function (arg1, arg2, arg3) {
    if (Array.isArray(arg2) && typeof arg3 === "function") {
        return this.send_command("hmget", [arg1].concat(arg2), arg3);
    } else if (Array.isArray(arg1) && typeof arg2 === "function") {
        return this.send_command("hmget", arg1, arg2);
    } else {
        return this.send_command("hmget", to_array(arguments));
Object.defineProperty(RedisClient.prototype, 'offline_queue_length', {
    get: function () {
        return this.offline_queue.length;
    }
};
RedisClient.prototype.HMGET = RedisClient.prototype.hmget;
RedisClient.prototype.hmset = function (args, callback) {
    var tmp_args, tmp_keys, i, il, key;
});
    if (Array.isArray(args) && typeof callback === "function") {
        return this.send_command("hmset", args, callback);
// Add support for camelCase by adding read only properties to the client
// All known exposed snake_case variables are added here
Object.defineProperty(RedisClient.prototype, 'retryDelay', {
    get: function () {
        return this.retry_delay;
    }
});
    args = to_array(arguments);
    if (typeof args[args.length - 1] === "function") {
        callback = args[args.length - 1];
        args.length -= 1;
    } else {
        callback = null;
Object.defineProperty(RedisClient.prototype, 'retryBackoff', {
    get: function () {
        return this.retry_backoff;
    }
});
    if (args.length === 2 && (typeof args[0] === "string" || typeof args[0] === "number") && typeof args[1] === "object") {
        // User does: client.hmset(key, {key1: val1, key2: val2})
        // assuming key is a string, i.e. email address
        // if key is a number, i.e. timestamp, convert to string
        if (typeof args[0] === "number") {
            args[0] = args[0].toString();
        }
        tmp_args = [ args[0] ];
        tmp_keys = Object.keys(args[1]);
        for (i = 0, il = tmp_keys.length; i < il ; i++) {
            key = tmp_keys[i];
            tmp_args.push(key);
            tmp_args.push(args[1][key]);
        }
        args = tmp_args;
Object.defineProperty(RedisClient.prototype, 'commandQueueLength', {
    get: function () {
        return this.command_queue.length;
    }
});
    return this.send_command("hmset", args, callback);
};
RedisClient.prototype.HMSET = RedisClient.prototype.hmset;
Multi.prototype.hmset = function () {
    var args = to_array(arguments), tmp_args;
    if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] === "object") {
        tmp_args = [ "hmset", args[0] ];
        Object.keys(args[1]).map(function (key) {
            tmp_args.push(key);
            tmp_args.push(args[1][key]);
        });
        if (args[2]) {
            tmp_args.push(args[2]);
        }
        args = tmp_args;
    } else {
        args.unshift("hmset");
Object.defineProperty(RedisClient.prototype, 'offlineQueueLength', {
    get: function () {
        return this.offline_queue.length;
    }
});
    this.queue.push(args);
    return this;
};
Multi.prototype.HMSET = Multi.prototype.hmset;
Multi.prototype.exec = function (callback) {
    var self = this;
    var errors = [];
    // drain queue, callback will catch "QUEUED" or error
    // TODO - get rid of all of these anonymous functions which are elegant but slow
    this.queue.forEach(function (args, index) {
        var command = args[0], obj;
        if (typeof args[args.length - 1] === "function") {
            args = args.slice(1, -1);
        } else {
            args = args.slice(1);
        }
        if (args.length === 1 && Array.isArray(args[0])) {
            args = args[0];
        }
        if (command.toLowerCase() === 'hmset' && typeof args[1] === 'object') {
            obj = args.pop();
            Object.keys(obj).forEach(function (key) {
                args.push(key);
                args.push(obj[key]);
            });
        }
        this._client.send_command(command, args, function (err, reply) {
            if (err) {
                var cur = self.queue[index];
                if (typeof cur[cur.length - 1] === "function") {
                    cur[cur.length - 1](err);
                } else {
                    errors.push(new Error(err));
                }
            }
        });
    }, this);
    // TODO - make this callback part of Multi.prototype instead of creating it each time
    return this._client.send_command("EXEC", [], function (err, replies) {
        if (err) {
            if (callback) {
                errors.push(new Error(err));
                callback(errors);
                return;
            } else {
                throw new Error(err);
            }
        }
        var i, il, reply, args;
        if (replies) {
            for (i = 1, il = self.queue.length; i < il; i += 1) {
                reply = replies[i - 1];
                args = self.queue[i];
                // TODO - confusing and error-prone that hgetall is special cased in two places
                if (reply && args[0].toLowerCase() === "hgetall") {
                    replies[i - 1] = reply = reply_to_object(reply);
                }
                if (typeof args[args.length - 1] === "function") {
                    args[args.length - 1](null, reply);
                }
            }
        }
        if (callback) {
            callback(null, replies);
        }
    });
};
Multi.prototype.EXEC = Multi.prototype.exec;
RedisClient.prototype.multi = function (args) {
    return new Multi(this, args);
};
RedisClient.prototype.MULTI = function (args) {
    return new Multi(this, args);
};
// stash original eval method
var eval_orig = RedisClient.prototype.eval;
// hook eval with an attempt to evalsha for cached scripts
RedisClient.prototype.eval = RedisClient.prototype.EVAL = function () {
    var self = this,
        args = to_array(arguments),
        callback;
    if (typeof args[args.length - 1] === "function") {
        callback = args.pop();
Object.defineProperty(RedisClient.prototype, 'shouldBuffer', {
    get: function () {
        return this.should_buffer;
    }
});
    if (Array.isArray(args[0])) {
        args = args[0];
Object.defineProperty(RedisClient.prototype, 'connectionId', {
    get: function () {
        return this.connection_id;
    }
});
    // replace script source with sha value
    var source = args[0];
    args[0] = crypto.createHash("sha1").update(source).digest("hex");
    self.evalsha(args, function (err, reply) {
        if (err && /NOSCRIPT/.test(err.message)) {
            args[0] = source;
            eval_orig.call(self, args, callback);
        } else if (callback) {
            callback(err, reply);
        }
    });
};
exports.createClient = function(arg0, arg1, arg2){
    if( arguments.length === 0 ){
        // createClient()
        return createClient_tcp(default_port, default_host, {});
    } else if( typeof arg0 === 'number' ||
        typeof arg0 === 'string' && arg0.match(/^\d+$/) ){
        // createClient( 3000, host, options)
        // createClient('3000', host, options)
        return createClient_tcp(arg0, arg1, arg2);
    } else if( typeof arg0 === 'string' ){
        // createClient( '/tmp/redis.sock', options)
        return createClient_unix(arg0,arg1);
    } else if( arg0 !== null && typeof arg0 === 'object' ){
        // createClient(options)
        return createClient_tcp(default_port, default_host, arg0 );
    } else if( arg0 === null && arg1 === null ){
        // for backward compatibility
        // createClient(null,null,options)
        return createClient_tcp(default_port, default_host, arg2);
    } else {
        throw new Error('unknown type of connection in createClient()');
Object.defineProperty(RedisClient.prototype, 'serverInfo', {
    get: function () {
        return this.server_info;
    }
}
var createClient_unix = function(path, options){
    var cnxOptions = {
        path: path
    };
    var net_client = net.createConnection(cnxOptions);
    var redis_client = new RedisClient(net_client, options || {});
    redis_client.connectionOption = cnxOptions;
    redis_client.address = path;
    return redis_client;
}
var createClient_tcp = function (port_arg, host_arg, options) {
    var cnxOptions = {
        'port' : port_arg || default_port,
        'host' : host_arg || default_host,
        'family' : (options && options.family === 'IPv6') ? 6 : 4
    };
    var net_client = net.createConnection(cnxOptions);
    var redis_client = new RedisClient(net_client, options || {});
    redis_client.connectionOption = cnxOptions;
    redis_client.address = cnxOptions.host + ':' + cnxOptions.port;
    return redis_client;
};
});
exports.print = function (err, reply) {
    if (err) {
        console.log("Error: " + err);
    } else {
        console.log("Reply: " + reply);
    }
exports.createClient = function () {
    return new RedisClient(unifyOptions.apply(null, arguments));
};
exports.RedisClient = RedisClient;
exports.print = utils.print;
exports.Multi = require('./lib/multi');
exports.AbortError = errorClasses.AbortError;
exports.ReplyError = Parser.ReplyError;
exports.AggregateError = errorClasses.AggregateError;
// Add all redis commands / node_redis api to the client
require('./lib/individualCommands');
require('./lib/extendedApi');
require('./lib/commands');

+ 0 - 1284
src/doctor/node_modules/redis/index.js.bak

@ -1,1284 +0,0 @@
/*global Buffer require exports console setTimeout */
var net = require("net"),
    util = require("./lib/util"),
    Queue = require("./lib/queue"),
    to_array = require("./lib/to_array"),
    events = require("events"),
    crypto = require("crypto"),
    parsers = [], commands,
    connection_id = 0,
    default_port = 6379,
    default_host = "127.0.0.1";
// can set this to true to enable for all connections
exports.debug_mode = false;
var arraySlice = Array.prototype.slice
function trace() {
    if (!exports.debug_mode) return;
    console.log.apply(null, arraySlice.call(arguments))
}
// hiredis might not be installed
try {
    require("./lib/parser/hiredis");
    parsers.push(require("./lib/parser/hiredis"));
} catch (err) {
    if (exports.debug_mode) {
        console.warn("hiredis parser not installed.");
    }
}
parsers.push(require("./lib/parser/javascript"));
function RedisClient(stream, options) {
    this.stream = stream;
    this.options = options = options || {};
    this.connection_id = ++connection_id;
    this.connected = false;
    this.ready = false;
    this.connections = 0;
    if (this.options.socket_nodelay === undefined) {
        this.options.socket_nodelay = true;
    }
    if (this.options.socket_keepalive === undefined) {
        this.options.socket_keepalive = true;
    }
    this.should_buffer = false;
    this.command_queue_high_water = this.options.command_queue_high_water || 1000;
    this.command_queue_low_water = this.options.command_queue_low_water || 0;
    this.max_attempts = null;
    if (options.max_attempts && !isNaN(options.max_attempts) && options.max_attempts > 0) {
        this.max_attempts = +options.max_attempts;
    }
    this.command_queue = new Queue(); // holds sent commands to de-pipeline them
    this.offline_queue = new Queue(); // holds commands issued but not able to be sent
    this.commands_sent = 0;
    this.connect_timeout = false;
    if (options.connect_timeout && !isNaN(options.connect_timeout) && options.connect_timeout > 0) {
        this.connect_timeout = +options.connect_timeout;
    }
    this.enable_offline_queue = true;
    if (typeof this.options.enable_offline_queue === "boolean") {
        this.enable_offline_queue = this.options.enable_offline_queue;
    }
    this.retry_max_delay = null;
    if (options.retry_max_delay !== undefined && !isNaN(options.retry_max_delay) && options.retry_max_delay > 0) {
        this.retry_max_delay = options.retry_max_delay;
    }
    this.initialize_retry_vars();
    this.pub_sub_mode = false;
    this.subscription_set = {};
    this.monitoring = false;
    this.closing = false;
    this.server_info = {};
    this.auth_pass = null;
    if (options.auth_pass !== undefined) {
        this.auth_pass = options.auth_pass;
    }
    this.parser_module = null;
    this.selected_db = null;	// save the selected db here, used when reconnecting
    this.old_state = null;
    this.install_stream_listeners();
    events.EventEmitter.call(this);
}
util.inherits(RedisClient, events.EventEmitter);
exports.RedisClient = RedisClient;
RedisClient.prototype.install_stream_listeners = function() {
    var self = this;
    this.stream.on("connect", function () {
        self.on_connect();
    });
    this.stream.on("data", function (buffer_from_socket) {
        self.on_data(buffer_from_socket);
    });
    this.stream.on("error", function (msg) {
        self.on_error(msg.message);
    });
    this.stream.on("close", function () {
        self.connection_gone("close");
    });
    this.stream.on("end", function () {
        self.connection_gone("end");
    });
    this.stream.on("drain", function () {
        self.should_buffer = false;
        self.emit("drain");
    });
};
RedisClient.prototype.initialize_retry_vars = function () {
    this.retry_timer = null;
    this.retry_totaltime = 0;
    this.retry_delay = 150;
    this.retry_backoff = 1.7;
    this.attempts = 1;
};
RedisClient.prototype.unref = function () {
    trace("User requesting to unref the connection");
    if (this.connected) {
        trace("unref'ing the socket connection");
        this.stream.unref();
    }
    else {
        trace("Not connected yet, will unref later");
        this.once("connect", function () {
            this.unref();
        })
    }
};
// flush offline_queue and command_queue, erroring any items with a callback first
RedisClient.prototype.flush_and_error = function (message) {
    var command_obj, error;
    error = new Error(message);
    while (this.offline_queue.length > 0) {
        command_obj = this.offline_queue.shift();
        if (typeof command_obj.callback === "function") {
            try {
                command_obj.callback(error);
            } catch (callback_err) {
                process.nextTick(function () {
                    throw callback_err;
                });
            }
        }
    }
    this.offline_queue = new Queue();
    while (this.command_queue.length > 0) {
        command_obj = this.command_queue.shift();
        if (typeof command_obj.callback === "function") {
            try {
                command_obj.callback(error);
            } catch (callback_err) {
                process.nextTick(function () {
                    throw callback_err;
                });
            }
        }
    }
    this.command_queue = new Queue();
};
RedisClient.prototype.on_error = function (msg) {
    var message = "Redis connection to " + this.address + " failed - " + msg;
    if (this.closing) {
        return;
    }
    if (exports.debug_mode) {
        console.warn(message);
    }
    this.flush_and_error(message);
    this.connected = false;
    this.ready = false;
    this.emit("error", new Error(message));
    // "error" events get turned into exceptions if they aren't listened for.  If the user handled this error
    // then we should try to reconnect.
    this.connection_gone("error");
};
RedisClient.prototype.do_auth = function () {
    var self = this;
    if (exports.debug_mode) {
        console.log("Sending auth to " + self.address + " id " + self.connection_id);
    }
    self.send_anyway = true;
    self.send_command("auth", [this.auth_pass], function (err, res) {
        if (err) {
            if (err.toString().match("LOADING")) {
                // if redis is still loading the db, it will not authenticate and everything else will fail
                console.log("Redis still loading, trying to authenticate later");
                setTimeout(function () {
                    self.do_auth();
                }, 2000); // TODO - magic number alert
                return;
            } else if (err.toString().match("no password is set")) {
                console.log("Warning: Redis server does not require a password, but a password was supplied.")
                err = null;
                res = "OK";
            } else {
                return self.emit("error", new Error("Auth error: " + err.message));
            }
        }
        if (res.toString() !== "OK") {
            return self.emit("error", new Error("Auth failed: " + res.toString()));
        }
        if (exports.debug_mode) {
            console.log("Auth succeeded " + self.address + " id " + self.connection_id);
        }
        if (self.auth_callback) {
            self.auth_callback(err, res);
            self.auth_callback = null;
        }
        // now we are really connected
        self.emit("connect");
        self.initialize_retry_vars();
        if (self.options.no_ready_check) {
            self.on_ready();
        } else {
            self.ready_check();
        }
    });
    self.send_anyway = false;
};
RedisClient.prototype.on_connect = function () {
    if (exports.debug_mode) {
        console.log("Stream connected " + this.address + " id " + this.connection_id);
    }
    this.connected = true;
    this.ready = false;
    this.connections += 1;
    this.command_queue = new Queue();
    this.emitted_end = false;
    if (this.options.socket_nodelay) {
        this.stream.setNoDelay();
    }
    this.stream.setKeepAlive(this.options.socket_keepalive);
    this.stream.setTimeout(0);
    this.init_parser();
    if (this.auth_pass) {
        this.do_auth();
    } else {
        this.emit("connect");
        this.initialize_retry_vars();
        if (this.options.no_ready_check) {
            this.on_ready();
        } else {
            this.ready_check();
        }
    }
};
RedisClient.prototype.init_parser = function () {
    var self = this;
    if (this.options.parser) {
        if (! parsers.some(function (parser) {
            if (parser.name === self.options.parser) {
                self.parser_module = parser;
                if (exports.debug_mode) {
                    console.log("Using parser module: " + self.parser_module.name);
                }
                return true;
            }
        })) {
            throw new Error("Couldn't find named parser " + self.options.parser + " on this system");
        }
    } else {
        if (exports.debug_mode) {
            console.log("Using default parser module: " + parsers[0].name);
        }
        this.parser_module = parsers[0];
    }
    this.parser_module.debug_mode = exports.debug_mode;
    // return_buffers sends back Buffers from parser to callback. detect_buffers sends back Buffers from parser, but
    // converts to Strings if the input arguments are not Buffers.
    this.reply_parser = new this.parser_module.Parser({
        return_buffers: self.options.return_buffers || self.options.detect_buffers || false
    });
    // "reply error" is an error sent back by Redis
    this.reply_parser.on("reply error", function (reply) {
        if (reply instanceof Error) {
            self.return_error(reply);
        } else {
            self.return_error(new Error(reply));
        }
    });
    this.reply_parser.on("reply", function (reply) {
        self.return_reply(reply);
    });
    // "error" is bad.  Somehow the parser got confused.  It'll try to reset and continue.
    this.reply_parser.on("error", function (err) {
        self.emit("error", new Error("Redis reply parser error: " + err.stack));
    });
};
RedisClient.prototype.on_ready = function () {
    var self = this;
    this.ready = true;
    if (this.old_state !== null) {
        this.monitoring = this.old_state.monitoring;
        this.pub_sub_mode = this.old_state.pub_sub_mode;
        this.selected_db = this.old_state.selected_db;
        this.old_state = null;
    }
    // magically restore any modal commands from a previous connection
    if (this.selected_db !== null) {
        // this trick works if and only if the following send_command
        // never goes into the offline queue
        var pub_sub_mode = this.pub_sub_mode;
        this.pub_sub_mode = false;
        this.send_command('select', [this.selected_db]);
        this.pub_sub_mode = pub_sub_mode;
    }
    if (this.pub_sub_mode === true) {
        // only emit "ready" when all subscriptions were made again
        var callback_count = 0;
        var callback = function () {
            callback_count--;
            if (callback_count === 0) {
                self.emit("ready");
            }
        };
        Object.keys(this.subscription_set).forEach(function (key) {
            var parts = key.split(" ");
            if (exports.debug_mode) {
                console.warn("sending pub/sub on_ready " + parts[0] + ", " + parts[1]);
            }
            callback_count++;
            self.send_command(parts[0] + "scribe", [parts[1]], callback);
        });
        return;
    } else if (this.monitoring) {
        this.send_command("monitor");
    } else {
        this.send_offline_queue();
    }
    this.emit("ready");
};
RedisClient.prototype.on_info_cmd = function (err, res) {
    var self = this, obj = {}, lines, retry_time;
    if (err) {
        return self.emit("error", new Error("Ready check failed: " + err.message));
    }
    lines = res.toString().split("\r\n");
    lines.forEach(function (line) {
        var parts = line.split(':');
        if (parts[1]) {
            obj[parts[0]] = parts[1];
        }
    });
    obj.versions = [];
    if( obj.redis_version ){
        obj.redis_version.split('.').forEach(function (num) {
            obj.versions.push(+num);
        });
    }
    // expose info key/vals to users
    this.server_info = obj;
    if (!obj.loading || (obj.loading && obj.loading === "0")) {
        if (exports.debug_mode) {
            console.log("Redis server ready.");
        }
        this.on_ready();
    } else {
        retry_time = obj.loading_eta_seconds * 1000;
        if (retry_time > 1000) {
            retry_time = 1000;
        }
        if (exports.debug_mode) {
            console.log("Redis server still loading, trying again in " + retry_time);
        }
        setTimeout(function () {
            self.ready_check();
        }, retry_time);
    }
};
RedisClient.prototype.ready_check = function () {
    var self = this;
    if (exports.debug_mode) {
        console.log("checking server ready state...");
    }
    this.send_anyway = true;  // secret flag to send_command to send something even if not "ready"
    this.info(function (err, res) {
        self.on_info_cmd(err, res);
    });
    this.send_anyway = false;
};
RedisClient.prototype.send_offline_queue = function () {
    var command_obj, buffered_writes = 0;
    while (this.offline_queue.length > 0) {
        command_obj = this.offline_queue.shift();
        if (exports.debug_mode) {
            console.log("Sending offline command: " + command_obj.command);
        }
        buffered_writes += !this.send_command(command_obj.command, command_obj.args, command_obj.callback);
    }
    this.offline_queue = new Queue();
    // Even though items were shifted off, Queue backing store still uses memory until next add, so just get a new Queue
    if (!buffered_writes) {
        this.should_buffer = false;
        this.emit("drain");
    }
};
RedisClient.prototype.connection_gone = function (why) {
    var self = this;
    // If a retry is already in progress, just let that happen
    if (this.retry_timer) {
        return;
    }
    if (exports.debug_mode) {
        console.warn("Redis connection is gone from " + why + " event.");
    }
    this.connected = false;
    this.ready = false;
    if (this.old_state === null) {
        var state = {
            monitoring: this.monitoring,
            pub_sub_mode: this.pub_sub_mode,
            selected_db: this.selected_db
        };
        this.old_state = state;
        this.monitoring = false;
        this.pub_sub_mode = false;
        this.selected_db = null;
    }
    // since we are collapsing end and close, users don't expect to be called twice
    if (! this.emitted_end) {
        this.emit("end");
        this.emitted_end = true;
    }
    this.flush_and_error("Redis connection gone from " + why + " event.");
    // If this is a requested shutdown, then don't retry
    if (this.closing) {
        this.retry_timer = null;
        if (exports.debug_mode) {
            console.warn("connection ended from quit command, not retrying.");
        }
        return;
    }
    var nextDelay = Math.floor(this.retry_delay * this.retry_backoff);
    if (this.retry_max_delay !== null && nextDelay > this.retry_max_delay) {
        this.retry_delay = this.retry_max_delay;
    } else {
        this.retry_delay = nextDelay;
    }
    if (exports.debug_mode) {
        console.log("Retry connection in " + this.retry_delay + " ms");
    }
    if (this.max_attempts && this.attempts >= this.max_attempts) {
        this.retry_timer = null;
        // TODO - some people need a "Redis is Broken mode" for future commands that errors immediately, and others
        // want the program to exit.  Right now, we just log, which doesn't really help in either case.
        console.error("node_redis: Couldn't get Redis connection after " + this.max_attempts + " attempts.");
        return;
    }
    this.attempts += 1;
    this.emit("reconnecting", {
        delay: self.retry_delay,
        attempt: self.attempts
    });
    this.retry_timer = setTimeout(function () {
        if (exports.debug_mode) {
            console.log("Retrying connection...");
        }
        self.retry_totaltime += self.retry_delay;
        if (self.connect_timeout && self.retry_totaltime >= self.connect_timeout) {
            self.retry_timer = null;
            // TODO - engage Redis is Broken mode for future commands, or whatever
            console.error("node_redis: Couldn't get Redis connection after " + self.retry_totaltime + "ms.");
            return;
        }
        self.stream = net.createConnection(self.connectionOption);
        self.install_stream_listeners();
        self.retry_timer = null;
    }, this.retry_delay);
};
RedisClient.prototype.on_data = function (data) {
    if (exports.debug_mode) {
        console.log("net read " + this.address + " id " + this.connection_id + ": " + data.toString());
    }
    try {
        this.reply_parser.execute(data);
    } catch (err) {
        // This is an unexpected parser problem, an exception that came from the parser code itself.
        // Parser should emit "error" events if it notices things are out of whack.
        // Callbacks that throw exceptions will land in return_reply(), below.
        // TODO - it might be nice to have a different "error" event for different types of errors
        this.emit("error", err);
    }
};
RedisClient.prototype.return_error = function (err) {
    var command_obj = this.command_queue.shift(), queue_len = this.command_queue.getLength();
    if (this.pub_sub_mode === false && queue_len === 0) {
        this.command_queue = new Queue();
        this.emit("idle");
    }
    if (this.should_buffer && queue_len <= this.command_queue_low_water) {
        this.emit("drain");
        this.should_buffer = false;
    }
    if (command_obj && typeof command_obj.callback === "function") {
        try {
            command_obj.callback(err);
        } catch (callback_err) {
            // if a callback throws an exception, re-throw it on a new stack so the parser can keep going
            process.nextTick(function () {
                throw callback_err;
            });
        }
    } else {
        console.log("node_redis: no callback to send error: " + err.message);
        // this will probably not make it anywhere useful, but we might as well throw
        process.nextTick(function () {
            throw err;
        });
    }
};
// if a callback throws an exception, re-throw it on a new stack so the parser can keep going.
// if a domain is active, emit the error on the domain, which will serve the same function.
// put this try/catch in its own function because V8 doesn't optimize this well yet.
function try_callback(callback, reply) {
    try {
        callback(null, reply);
    } catch (err) {
        if (process.domain) {
            var currDomain = process.domain;
            currDomain.emit('error', err);
            if (process.domain === currDomain) {
                currDomain.exit();
            }
        } else {
            process.nextTick(function () {
                throw err;
            });
        }
    }
}
// hgetall converts its replies to an Object.  If the reply is empty, null is returned.
function reply_to_object(reply) {
    var obj = {}, j, jl, key, val;
    if (reply.length === 0) {
        return null;
    }
    for (j = 0, jl = reply.length; j < jl; j += 2) {
        key = reply[j].toString('binary');
        val = reply[j + 1];
        obj[key] = val;
    }
    return obj;
}
function reply_to_strings(reply) {
    var i;
    if (Buffer.isBuffer(reply)) {
        return reply.toString();
    }
    if (Array.isArray(reply)) {
        for (i = 0; i < reply.length; i++) {
            if (reply[i] !== null && reply[i] !== undefined) {
                reply[i] = reply[i].toString();
            }
        }
        return reply;
    }
    return reply;
}
RedisClient.prototype.return_reply = function (reply) {
    var command_obj, len, type, timestamp, argindex, args, queue_len;
    // If the "reply" here is actually a message received asynchronously due to a
    // pubsub subscription, don't pop the command queue as we'll only be consuming
    // the head command prematurely.
    if (Array.isArray(reply) && reply.length > 0 && reply[0]) {
        type = reply[0].toString();
    }
    if (this.pub_sub_mode && (type == 'message' || type == 'pmessage')) {
        trace("received pubsub message");
    }
    else {
        command_obj = this.command_queue.shift();
    }
    queue_len = this.command_queue.getLength();
    if (this.pub_sub_mode === false && queue_len === 0) {
        this.command_queue = new Queue();  // explicitly reclaim storage from old Queue
        this.emit("idle");
    }
    if (this.should_buffer && queue_len <= this.command_queue_low_water) {
        this.emit("drain");
        this.should_buffer = false;
    }
    if (command_obj && !command_obj.sub_command) {
        if (typeof command_obj.callback === "function") {
            if (this.options.detect_buffers && command_obj.buffer_args === false) {
                // If detect_buffers option was specified, then the reply from the parser will be Buffers.
                // If this command did not use Buffer arguments, then convert the reply to Strings here.
                reply = reply_to_strings(reply);
            }
            // TODO - confusing and error-prone that hgetall is special cased in two places
            if (reply && 'hgetall' === command_obj.command.toLowerCase()) {
                reply = reply_to_object(reply);
            }
            try_callback(command_obj.callback, reply);
        } else if (exports.debug_mode) {
            console.log("no callback for reply: " + (reply && reply.toString && reply.toString()));
        }
    } else if (this.pub_sub_mode || (command_obj && command_obj.sub_command)) {
        if (Array.isArray(reply)) {
            type = reply[0].toString();
            if (type === "message") {
                this.emit("message", reply[1].toString(), reply[2]); // channel, message
            } else if (type === "pmessage") {
                this.emit("pmessage", reply[1].toString(), reply[2].toString(), reply[3]); // pattern, channel, message
            } else if (type === "subscribe" || type === "unsubscribe" || type === "psubscribe" || type === "punsubscribe") {
                if (reply[2] === 0) {
                    this.pub_sub_mode = false;
                    if (this.debug_mode) {
                        console.log("All subscriptions removed, exiting pub/sub mode");
                    }
                } else {
                    this.pub_sub_mode = true;
                }
                // subscribe commands take an optional callback and also emit an event, but only the first response is included in the callback
                // TODO - document this or fix it so it works in a more obvious way
                // reply[1] can be null
                var reply1String = (reply[1] === null) ? null : reply[1].toString();
                if (command_obj && typeof command_obj.callback === "function") {
                    try_callback(command_obj.callback, reply1String);
                }
                this.emit(type, reply1String, reply[2]); // channel, count
            } else {
                throw new Error("subscriptions are active but got unknown reply type " + type);
            }
        } else if (! this.closing) {
            throw new Error("subscriptions are active but got an invalid reply: " + reply);
        }
    } else if (this.monitoring) {
        len = reply.indexOf(" ");
        timestamp = reply.slice(0, len);
        argindex = reply.indexOf('"');
        args = reply.slice(argindex + 1, -1).split('" "').map(function (elem) {
            return elem.replace(/\\"/g, '"');
        });
        this.emit("monitor", timestamp, args);
    } else {
        throw new Error("node_redis command queue state error. If you can reproduce this, please report it.");
    }
};
// This Command constructor is ever so slightly faster than using an object literal, but more importantly, using
// a named constructor helps it show up meaningfully in the V8 CPU profiler and in heap snapshots.
function Command(command, args, sub_command, buffer_args, callback) {
    this.command = command;
    this.args = args;
    this.sub_command = sub_command;
    this.buffer_args = buffer_args;
    this.callback = callback;
}
RedisClient.prototype.send_command = function (command, args, callback) {
    var arg, command_obj, i, il, elem_count, buffer_args, stream = this.stream, command_str = "", buffered_writes = 0, last_arg_type, lcaseCommand;
    if (typeof command !== "string") {
        throw new Error("First argument to send_command must be the command name string, not " + typeof command);
    }
    if (Array.isArray(args)) {
        if (typeof callback === "function") {
            // probably the fastest way:
            //     client.command([arg1, arg2], cb);  (straight passthrough)
            //         send_command(command, [arg1, arg2], cb);
        } else if (! callback) {
            // most people find this variable argument length form more convenient, but it uses arguments, which is slower
            //     client.command(arg1, arg2, cb);   (wraps up arguments into an array)
            //       send_command(command, [arg1, arg2, cb]);
            //     client.command(arg1, arg2);   (callback is optional)
            //       send_command(command, [arg1, arg2]);
            //     client.command(arg1, arg2, undefined);   (callback is undefined)
            //       send_command(command, [arg1, arg2, undefined]);
            last_arg_type = typeof args[args.length - 1];
            if (last_arg_type === "function" || last_arg_type === "undefined") {
                callback = args.pop();
            }
        } else {
            throw new Error("send_command: last argument must be a callback or undefined");
        }
    } else {
        throw new Error("send_command: second argument must be an array");
    }
    if (callback && process.domain) callback = process.domain.bind(callback);
    // if the last argument is an array and command is sadd or srem, expand it out:
    //     client.sadd(arg1, [arg2, arg3, arg4], cb);
    //  converts to:
    //     client.sadd(arg1, arg2, arg3, arg4, cb);
    lcaseCommand = command.toLowerCase();
    if ((lcaseCommand === 'sadd' || lcaseCommand === 'srem') && args.length > 0 && Array.isArray(args[args.length - 1])) {
        args = args.slice(0, -1).concat(args[args.length - 1]);
    }
    // if the value is undefined or null and command is set or setx, need not to send message to redis
    if (command === 'set' || command === 'setex') {
        if(args[args.length - 1] === undefined || args[args.length - 1] === null) {
            var err = new Error('send_command: ' + command + ' value must not be undefined or null');
            return callback && callback(err);
        }
    }
    buffer_args = false;
    for (i = 0, il = args.length, arg; i < il; i += 1) {
        if (Buffer.isBuffer(args[i])) {
            buffer_args = true;
        }
    }
    command_obj = new Command(command, args, false, buffer_args, callback);
    if ((!this.ready && !this.send_anyway) || !stream.writable) {
        if (exports.debug_mode) {
            if (!stream.writable) {
                console.log("send command: stream is not writeable.");
            }
        }
        if (this.enable_offline_queue) {
            if (exports.debug_mode) {
                console.log("Queueing " + command + " for next server connection.");
            }
            this.offline_queue.push(command_obj);
            this.should_buffer = true;
        } else {
            var not_writeable_error = new Error('send_command: stream not writeable. enable_offline_queue is false');
            if (command_obj.callback) {
                command_obj.callback(not_writeable_error);
            } else {
                throw not_writeable_error;
            }
        }
        return false;
    }
    if (command === "subscribe" || command === "psubscribe" || command === "unsubscribe" || command === "punsubscribe") {
        this.pub_sub_command(command_obj);
    } else if (command === "monitor") {
        this.monitoring = true;
    } else if (command === "quit") {
        this.closing = true;
    } else if (this.pub_sub_mode === true) {
        throw new Error("Connection in subscriber mode, only subscriber commands may be used");
    }
    this.command_queue.push(command_obj);
    this.commands_sent += 1;
    elem_count = args.length + 1;
    // Always use "Multi bulk commands", but if passed any Buffer args, then do multiple writes, one for each arg.
    // This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer.
    command_str = "*" + elem_count + "\r\n$" + command.length + "\r\n" + command + "\r\n";
    if (! buffer_args) { // Build up a string and send entire command in one write
        for (i = 0, il = args.length, arg; i < il; i += 1) {
            arg = args[i];
            if (typeof arg !== "string") {
                arg = String(arg);
            }
            command_str += "$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n";
        }
        if (exports.debug_mode) {
            console.log("send " + this.address + " id " + this.connection_id + ": " + command_str);
        }
        buffered_writes += !stream.write(command_str);
    } else {
        if (exports.debug_mode) {
            console.log("send command (" + command_str + ") has Buffer arguments");
        }
        buffered_writes += !stream.write(command_str);
        for (i = 0, il = args.length, arg; i < il; i += 1) {
            arg = args[i];
            if (!(Buffer.isBuffer(arg) || arg instanceof String)) {
                arg = String(arg);
            }
            if (Buffer.isBuffer(arg)) {
                if (arg.length === 0) {
                    if (exports.debug_mode) {
                        console.log("send_command: using empty string for 0 length buffer");
                    }
                    buffered_writes += !stream.write("$0\r\n\r\n");
                } else {
                    buffered_writes += !stream.write("$" + arg.length + "\r\n");
                    buffered_writes += !stream.write(arg);
                    buffered_writes += !stream.write("\r\n");
                    if (exports.debug_mode) {
                        console.log("send_command: buffer send " + arg.length + " bytes");
                    }
                }
            } else {
                if (exports.debug_mode) {
                    console.log("send_command: string send " + Buffer.byteLength(arg) + " bytes: " + arg);
                }
                buffered_writes += !stream.write("$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n");
            }
        }
    }
    if (exports.debug_mode) {
        console.log("send_command buffered_writes: " + buffered_writes, " should_buffer: " + this.should_buffer);
    }
    if (buffered_writes || this.command_queue.getLength() >= this.command_queue_high_water) {
        this.should_buffer = true;
    }
    return !this.should_buffer;
};
RedisClient.prototype.pub_sub_command = function (command_obj) {
    var i, key, command, args;
    if (this.pub_sub_mode === false && exports.debug_mode) {
        console.log("Entering pub/sub mode from " + command_obj.command);
    }
    this.pub_sub_mode = true;
    command_obj.sub_command = true;
    command = command_obj.command;
    args = command_obj.args;
    if (command === "subscribe" || command === "psubscribe") {
        if (command === "subscribe") {
            key = "sub";
        } else {
            key = "psub";
        }
        for (i = 0; i < args.length; i++) {
            this.subscription_set[key + " " + args[i]] = true;
        }
    } else {
        if (command === "unsubscribe") {
            key = "sub";
        } else {
            key = "psub";
        }
        for (i = 0; i < args.length; i++) {
            delete this.subscription_set[key + " " + args[i]];
        }
    }
};
RedisClient.prototype.end = function () {
    this.stream._events = {};
    //clear retry_timer
    if(this.retry_timer){
        clearTimeout(this.retry_timer);
        this.retry_timer=null;
    }
    this.stream.on("error", function(){});
    this.connected = false;
    this.ready = false;
    this.closing = true;
    return this.stream.destroySoon();
};
function Multi(client, args) {
    this._client = client;
    this.queue = [["MULTI"]];
    if (Array.isArray(args)) {
        this.queue = this.queue.concat(args);
    }
}
exports.Multi = Multi;
// take 2 arrays and return the union of their elements
function set_union(seta, setb) {
    var obj = {};
    seta.forEach(function (val) {
        obj[val] = true;
    });
    setb.forEach(function (val) {
        obj[val] = true;
    });
    return Object.keys(obj);
}
// This static list of commands is updated from time to time.  ./lib/commands.js can be updated with generate_commands.js
commands = set_union(["get", "set", "setnx", "setex", "append", "strlen", "del", "exists", "setbit", "getbit", "setrange", "getrange", "substr",
    "incr", "decr", "mget", "rpush", "lpush", "rpushx", "lpushx", "linsert", "rpop", "lpop", "brpop", "brpoplpush", "blpop", "llen", "lindex",
    "lset", "lrange", "ltrim", "lrem", "rpoplpush", "sadd", "srem", "smove", "sismember", "scard", "spop", "srandmember", "sinter", "sinterstore",
    "sunion", "sunionstore", "sdiff", "sdiffstore", "smembers", "zadd", "zincrby", "zrem", "zremrangebyscore", "zremrangebyrank", "zunionstore",
    "zinterstore", "zrange", "zrangebyscore", "zrevrangebyscore", "zcount", "zrevrange", "zcard", "zscore", "zrank", "zrevrank", "hset", "hsetnx",
    "hget", "hmset", "hmget", "hincrby", "hdel", "hlen", "hkeys", "hvals", "hgetall", "hexists", "incrby", "decrby", "getset", "mset", "msetnx",
    "randomkey", "select", "move", "rename", "renamenx", "expire", "expireat", "keys", "dbsize", "auth", "ping", "echo", "save", "bgsave",
    "bgrewriteaof", "shutdown", "lastsave", "type", "multi", "exec", "discard", "sync", "flushdb", "flushall", "sort", "info", "monitor", "ttl",
    "persist", "slaveof", "debug", "config", "subscribe", "unsubscribe", "psubscribe", "punsubscribe", "publish", "watch", "unwatch", "cluster",
    "restore", "migrate", "dump", "object", "client", "eval", "evalsha"], require("./lib/commands"));
commands.forEach(function (fullCommand) {
    var command = fullCommand.split(' ')[0];
    RedisClient.prototype[command] = function (args, callback) {
        if (Array.isArray(args) && typeof callback === "function") {
            return this.send_command(command, args, callback);
        } else {
            return this.send_command(command, to_array(arguments));
        }
    };
    RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command];
    Multi.prototype[command] = function () {
        this.queue.push([command].concat(to_array(arguments)));
        return this;
    };
    Multi.prototype[command.toUpperCase()] = Multi.prototype[command];
});
// store db in this.select_db to restore it on reconnect
RedisClient.prototype.select = function (db, callback) {
    var self = this;
    this.send_command('select', [db], function (err, res) {
        if (err === null) {
            self.selected_db = db;
        }
        if (typeof(callback) === 'function') {
            callback(err, res);
        } else if (err) {
            self.emit('error', err);
        }
    });
};
RedisClient.prototype.SELECT = RedisClient.prototype.select;
// Stash auth for connect and reconnect.  Send immediately if already connected.
RedisClient.prototype.auth = function () {
    var args = to_array(arguments);
    this.auth_pass = args[0];
    this.auth_callback = args[1];
    if (exports.debug_mode) {
        console.log("Saving auth as " + this.auth_pass);
    }
    if (this.connected) {
        this.send_command("auth", args);
    }
};
RedisClient.prototype.AUTH = RedisClient.prototype.auth;
RedisClient.prototype.hmget = function (arg1, arg2, arg3) {
    if (Array.isArray(arg2) && typeof arg3 === "function") {
        return this.send_command("hmget", [arg1].concat(arg2), arg3);
    } else if (Array.isArray(arg1) && typeof arg2 === "function") {
        return this.send_command("hmget", arg1, arg2);
    } else {
        return this.send_command("hmget", to_array(arguments));
    }
};
RedisClient.prototype.HMGET = RedisClient.prototype.hmget;
RedisClient.prototype.hmset = function (args, callback) {
    var tmp_args, tmp_keys, i, il, key;
    if (Array.isArray(args) && typeof callback === "function") {
        return this.send_command("hmset", args, callback);
    }
    args = to_array(arguments);
    if (typeof args[args.length - 1] === "function") {
        callback = args[args.length - 1];
        args.length -= 1;
    } else {
        callback = null;
    }
    if (args.length === 2 && (typeof args[0] === "string" || typeof args[0] === "number") && typeof args[1] === "object") {
        // User does: client.hmset(key, {key1: val1, key2: val2})
        // assuming key is a string, i.e. email address
        // if key is a number, i.e. timestamp, convert to string
        if (typeof args[0] === "number") {
            args[0] = args[0].toString();
        }
        tmp_args = [ args[0] ];
        tmp_keys = Object.keys(args[1]);
        for (i = 0, il = tmp_keys.length; i < il ; i++) {
            key = tmp_keys[i];
            tmp_args.push(key);
            tmp_args.push(args[1][key]);
        }
        args = tmp_args;
    }
    return this.send_command("hmset", args, callback);
};
RedisClient.prototype.HMSET = RedisClient.prototype.hmset;
Multi.prototype.hmset = function () {
    var args = to_array(arguments), tmp_args;
    if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] === "object") {
        tmp_args = [ "hmset", args[0] ];
        Object.keys(args[1]).map(function (key) {
            tmp_args.push(key);
            tmp_args.push(args[1][key]);
        });
        if (args[2]) {
            tmp_args.push(args[2]);
        }
        args = tmp_args;
    } else {
        args.unshift("hmset");
    }
    this.queue.push(args);
    return this;
};
Multi.prototype.HMSET = Multi.prototype.hmset;
Multi.prototype.exec = function (callback) {
    var self = this;
    var errors = [];
    // drain queue, callback will catch "QUEUED" or error
    // TODO - get rid of all of these anonymous functions which are elegant but slow
    this.queue.forEach(function (args, index) {
        var command = args[0], obj;
        if (typeof args[args.length - 1] === "function") {
            args = args.slice(1, -1);
        } else {
            args = args.slice(1);
        }
        if (args.length === 1 && Array.isArray(args[0])) {
            args = args[0];
        }
        if (command.toLowerCase() === 'hmset' && typeof args[1] === 'object') {
            obj = args.pop();
            Object.keys(obj).forEach(function (key) {
                args.push(key);
                args.push(obj[key]);
            });
        }
        this._client.send_command(command, args, function (err, reply) {
            if (err) {
                var cur = self.queue[index];
                if (typeof cur[cur.length - 1] === "function") {
                    cur[cur.length - 1](err);
                } else {
                    errors.push(new Error(err));
                }
            }
        });
    }, this);
    // TODO - make this callback part of Multi.prototype instead of creating it each time
    return this._client.send_command("EXEC", [], function (err, replies) {
        if (err) {
            if (callback) {
                errors.push(new Error(err));
                callback(errors);
                return;
            } else {
                throw new Error(err);
            }
        }
        var i, il, reply, args;
        if (replies) {
            for (i = 1, il = self.queue.length; i < il; i += 1) {
                reply = replies[i - 1];
                args = self.queue[i];
                // TODO - confusing and error-prone that hgetall is special cased in two places
                if (reply && args[0].toLowerCase() === "hgetall") {
                    replies[i - 1] = reply = reply_to_object(reply);
                }
                if (typeof args[args.length - 1] === "function") {
                    args[args.length - 1](null, reply);
                }
            }
        }
        if (callback) {
            callback(null, replies);
        }
    });
};
Multi.prototype.EXEC = Multi.prototype.exec;
RedisClient.prototype.multi = function (args) {
    return new Multi(this, args);
};
RedisClient.prototype.MULTI = function (args) {
    return new Multi(this, args);
};
// stash original eval method
var eval_orig = RedisClient.prototype.eval;
// hook eval with an attempt to evalsha for cached scripts
RedisClient.prototype.eval = RedisClient.prototype.EVAL = function () {
    var self = this,
        args = to_array(arguments),
        callback;
    if (typeof args[args.length - 1] === "function") {
        callback = args.pop();
    }
    if (Array.isArray(args[0])) {
        args = args[0];
    }
    // replace script source with sha value
    var source = args[0];
    args[0] = crypto.createHash("sha1").update(source).digest("hex");
    self.evalsha(args, function (err, reply) {
        if (err && /NOSCRIPT/.test(err.message)) {
            args[0] = source;
            eval_orig.call(self, args, callback);
        } else if (callback) {
            callback(err, reply);
        }
    });
};
exports.createClient = function(arg0, arg1, arg2){
    if( arguments.length === 0 ){
        // createClient()
        return createClient_tcp(default_port, default_host, {});
    } else if( typeof arg0 === 'number' ||
        typeof arg0 === 'string' && arg0.match(/^\d+$/) ){
        // createClient( 3000, host, options)
        // createClient('3000', host, options)
        return createClient_tcp(arg0, arg1, arg2);
    } else if( typeof arg0 === 'string' ){
        // createClient( '/tmp/redis.sock', options)
        return createClient_unix(arg0,arg1);
    } else if( arg0 !== null && typeof arg0 === 'object' ){
        // createClient(options)
        return createClient_tcp(default_port, default_host, arg0 );
    } else if( arg0 === null && arg1 === null ){
        // for backward compatibility
        // createClient(null,null,options)
        return createClient_tcp(default_port, default_host, arg2);
    } else {
        throw new Error('unknown type of connection in createClient()');
    }
}
var createClient_unix = function(path, options){
    var cnxOptions = {
        path: path
    };
    var net_client = net.createConnection(cnxOptions);
    var redis_client = new RedisClient(net_client, options || {});
    redis_client.connectionOption = cnxOptions;
    redis_client.address = path;
    return redis_client;
}
var createClient_tcp = function (port_arg, host_arg, options) {
    var cnxOptions = {
        'port' : port_arg || default_port,
        'host' : host_arg || default_host,
        'family' : (options && options.family === 'IPv6') ? 6 : 4
    };
    var net_client = net.createConnection(cnxOptions);
    var redis_client = new RedisClient(net_client, options || {});
    redis_client.connectionOption = cnxOptions;
    redis_client.address = cnxOptions.host + ':' + cnxOptions.port;
    return redis_client;
};
exports.print = function (err, reply) {
    if (err) {
        console.log("Error: " + err);
    } else {
        console.log("Reply: " + reply);
    }
};

+ 16 - 0
src/doctor/node_modules/redis/lib/command.js

@ -0,0 +1,16 @@
'use strict';
var betterStackTraces = /development/i.test(process.env.NODE_ENV) || /\bredis\b/i.test(process.env.NODE_DEBUG);
function Command (command, args, callback, call_on_write) {
    this.command = command;
    this.args = args;
    this.buffer_args = false;
    this.callback = callback;
    this.call_on_write = call_on_write;
    if (betterStackTraces) {
        this.error = new Error();
    }
}
module.exports = Command;

+ 113 - 163
src/doctor/node_modules/redis/lib/commands.js

@ -1,163 +1,113 @@
// This file was generated by ./generate_commands.js on Wed Apr 23 2014 14:51:21 GMT-0700 (PDT)
module.exports = [
    "append",
    "auth",
    "bgrewriteaof",
    "bgsave",
    "bitcount",
    "bitop",
    "bitpos",
    "blpop",
    "brpop",
    "brpoplpush",
    "client kill",
    "client list",
    "client getname",
    "client pause",
    "client setname",
    "config get",
    "config rewrite",
    "config set",
    "config resetstat",
    "dbsize",
    "debug object",
    "debug segfault",
    "decr",
    "decrby",
    "del",
    "discard",
    "dump",
    "echo",
    "eval",
    "evalsha",
    "exec",
    "exists",
    "expire",
    "expireat",
    "flushall",
    "flushdb",
    "get",
    "getbit",
    "getrange",
    "getset",
    "hdel",
    "hexists",
    "hget",
    "hgetall",
    "hincrby",
    "hincrbyfloat",
    "hkeys",
    "hlen",
    "hmget",
    "hmset",
    "hset",
    "hsetnx",
    "hvals",
    "incr",
    "incrby",
    "incrbyfloat",
    "info",
    "keys",
    "lastsave",
    "lindex",
    "linsert",
    "llen",
    "lpop",
    "lpush",
    "lpushx",
    "lrange",
    "lrem",
    "lset",
    "ltrim",
    "mget",
    "migrate",
    "monitor",
    "move",
    "mset",
    "msetnx",
    "multi",
    "object",
    "persist",
    "pexpire",
    "pexpireat",
    "pfadd",
    "pfcount",
    "pfmerge",
    "ping",
    "psetex",
    "psubscribe",
    "pubsub",
    "pttl",
    "publish",
    "punsubscribe",
    "quit",
    "randomkey",
    "rename",
    "renamenx",
    "restore",
    "rpop",
    "rpoplpush",
    "rpush",
    "rpushx",
    "sadd",
    "save",
    "scard",
    "script exists",
    "script flush",
    "script kill",
    "script load",
    "sdiff",
    "sdiffstore",
    "select",
    "set",
    "setbit",
    "setex",
    "setnx",
    "setrange",
    "shutdown",
    "sinter",
    "sinterstore",
    "sismember",
    "slaveof",
    "slowlog",
    "smembers",
    "smove",
    "sort",
    "spop",
    "srandmember",
    "srem",
    "strlen",
    "subscribe",
    "sunion",
    "sunionstore",
    "sync",
    "time",
    "ttl",
    "type",
    "unsubscribe",
    "unwatch",
    "watch",
    "zadd",
    "zcard",
    "zcount",
    "zincrby",
    "zinterstore",
    "zlexcount",
    "zrange",
    "zrangebylex",
    "zrangebyscore",
    "zrank",
    "zrem",
    "zremrangebylex",
    "zremrangebyrank",
    "zremrangebyscore",
    "zrevrange",
    "zrevrangebyscore",
    "zrevrank",
    "zscore",
    "zunionstore",
    "scan",
    "sscan",
    "hscan",
    "zscan"
];
'use strict';
var commands = require('redis-commands');
var Multi = require('./multi');
var RedisClient = require('../').RedisClient;
var Command = require('./command');
// Feature detect if a function may change it's name
var changeFunctionName = (function () {
    var fn = function abc () {};
    try {
        Object.defineProperty(fn, 'name', {
            value: 'foobar'
        });
        return true;
    } catch (e) {
        return false;
    }
}());
// TODO: Rewrite this including the invidual commands into a Commands class
// that provided a functionality to add new commands to the client
commands.list.forEach(function (command) {
    // Some rare Redis commands use special characters in their command name
    // Convert those to a underscore to prevent using invalid function names
    var commandName = command.replace(/(?:^([0-9])|[^a-zA-Z0-9_$])/g, '_$1');
    // Do not override existing functions
    if (!RedisClient.prototype[command]) {
        RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command] = function () {
            var arr;
            var len = arguments.length;
            var callback;
            var i = 0;
            if (Array.isArray(arguments[0])) {
                arr = arguments[0];
                if (len === 2) {
                    callback = arguments[1];
                }
            } else if (len > 1 && Array.isArray(arguments[1])) {
                if (len === 3) {
                    callback = arguments[2];
                }
                len = arguments[1].length;
                arr = new Array(len + 1);
                arr[0] = arguments[0];
                for (; i < len; i += 1) {
                    arr[i + 1] = arguments[1][i];
                }
            } else {
                // The later should not be the average use case
                if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
                    len--;
                    callback = arguments[len];
                }
                arr = new Array(len);
                for (; i < len; i += 1) {
                    arr[i] = arguments[i];
                }
            }
            return this.internal_send_command(new Command(command, arr, callback));
        };
        if (changeFunctionName) {
            Object.defineProperty(RedisClient.prototype[command], 'name', {
                value: commandName
            });
        }
    }
    // Do not override existing functions
    if (!Multi.prototype[command]) {
        Multi.prototype[command.toUpperCase()] = Multi.prototype[command] = function () {
            var arr;
            var len = arguments.length;
            var callback;
            var i = 0;
            if (Array.isArray(arguments[0])) {
                arr = arguments[0];
                if (len === 2) {
                    callback = arguments[1];
                }
            } else if (len > 1 && Array.isArray(arguments[1])) {
                if (len === 3) {
                    callback = arguments[2];
                }
                len = arguments[1].length;
                arr = new Array(len + 1);
                arr[0] = arguments[0];
                for (; i < len; i += 1) {
                    arr[i + 1] = arguments[1][i];
                }
            } else {
                // The later should not be the average use case
                if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
                    len--;
                    callback = arguments[len];
                }
                arr = new Array(len);
                for (; i < len; i += 1) {
                    arr[i] = arguments[i];
                }
            }
            this.queue.push(new Command(command, arr, callback));
            return this;
        };
        if (changeFunctionName) {
            Object.defineProperty(Multi.prototype[command], 'name', {
                value: commandName
            });
        }
    }
});

+ 79 - 0
src/doctor/node_modules/redis/lib/createClient.js

@ -0,0 +1,79 @@
'use strict';
var utils = require('./utils');
var URL = require('url');
module.exports = function createClient (port_arg, host_arg, options) {
    if (typeof port_arg === 'number' || typeof port_arg === 'string' && /^\d+$/.test(port_arg)) {
        var host;
        if (typeof host_arg === 'string') {
            host = host_arg;
        } else {
            if (options && host_arg) {
                throw new TypeError('Unknown type of connection in createClient()');
            }
            options = options || host_arg;
        }
        options = utils.clone(options);
        options.host = host || options.host;
        options.port = port_arg;
    } else if (typeof port_arg === 'string' || port_arg && port_arg.url) {
        options = utils.clone(port_arg.url ? port_arg : host_arg || options);
        var parsed = URL.parse(port_arg.url || port_arg, true, true);
        // [redis:]//[[user][:password]@][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]
        if (parsed.slashes) { // We require slashes
            if (parsed.auth) {
                options.password = parsed.auth.split(':')[1];
            }
            if (parsed.protocol && parsed.protocol !== 'redis:') {
                console.warn('node_redis: WARNING: You passed "' + parsed.protocol.substring(0, parsed.protocol.length - 1) + '" as protocol instead of the "redis" protocol!');
            }
            if (parsed.pathname && parsed.pathname !== '/') {
                options.db = parsed.pathname.substr(1);
            }
            if (parsed.hostname) {
                options.host = parsed.hostname;
            }
            if (parsed.port) {
                options.port = parsed.port;
            }
            if (parsed.search !== '') {
                var elem;
                for (elem in parsed.query) {
                    // If options are passed twice, only the parsed options will be used
                    if (elem in options) {
                        if (options[elem] === parsed.query[elem]) {
                            console.warn('node_redis: WARNING: You passed the ' + elem + ' option twice!');
                        } else {
                            throw new RangeError('The ' + elem + ' option is added twice and does not match');
                        }
                    }
                    options[elem] = parsed.query[elem];
                }
            }
        } else if (parsed.hostname) {
            throw new RangeError('The redis url must begin with slashes "//" or contain slashes after the redis protocol');
        } else {
            options.path = port_arg;
        }
    } else if (typeof port_arg === 'object' || port_arg === undefined) {
        options = utils.clone(port_arg || options);
        options.host = options.host || host_arg;
        if (port_arg && arguments.length !== 1) {
            throw new TypeError('To many arguments passed to createClient. Please only pass the options object');
        }
    }
    if (!options) {
        throw new TypeError('Unknown type of connection in createClient()');
    }
    return options;
};

+ 46 - 0
src/doctor/node_modules/redis/lib/customErrors.js

@ -0,0 +1,46 @@
'use strict';
var util = require('util');
function AbortError (obj) {
    Error.captureStackTrace(this, this.constructor);
    Object.defineProperty(this, 'message', {
        value: obj.message || '',
        configurable: true,
        writable: true
    });
    for (var keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) {
        this[key] = obj[key];
    }
}
function AggregateError (obj) {
    Error.captureStackTrace(this, this.constructor);
    Object.defineProperty(this, 'message', {
        value: obj.message || '',
        configurable: true,
        writable: true
    });
    for (var keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) {
        this[key] = obj[key];
    }
}
util.inherits(AbortError, Error);
util.inherits(AggregateError, AbortError);
Object.defineProperty(AbortError.prototype, 'name', {
    value: 'AbortError',
    // configurable: true,
    writable: true
});
Object.defineProperty(AggregateError.prototype, 'name', {
    value: 'AggregateError',
    // configurable: true,
    writable: true
});
module.exports = {
    AbortError: AbortError,
    AggregateError: AggregateError
};

+ 11 - 0
src/doctor/node_modules/redis/lib/debug.js

@ -0,0 +1,11 @@
'use strict';
var index = require('../');
function debug () {
    if (index.debug_mode) {
        console.error.apply(null, arguments);
    }
}
module.exports = debug;

+ 112 - 0
src/doctor/node_modules/redis/lib/extendedApi.js

@ -0,0 +1,112 @@
'use strict';
var utils = require('./utils');
var debug = require('./debug');
var RedisClient = require('../').RedisClient;
var Command = require('./command');
var noop = function () {};
/**********************************************
All documented and exposed API belongs in here
**********************************************/
// Redirect calls to the appropriate function and use to send arbitrary / not supported commands
RedisClient.prototype.send_command = RedisClient.prototype.sendCommand = function (command, args, callback) {
    // Throw to fail early instead of relying in order in this case
    if (typeof command !== 'string') {
        throw new TypeError('Wrong input type "' + (command !== null && command !== undefined ? command.constructor.name : command) + '" for command name');
    }
    if (!Array.isArray(args)) {
        if (args === undefined || args === null) {
            args = [];
        } else if (typeof args === 'function' && callback === undefined) {
            callback = args;
            args = [];
        } else {
            throw new TypeError('Wrong input type "' + args.constructor.name + '" for args');
        }
    }
    if (typeof callback !== 'function' && callback !== undefined) {
        throw new TypeError('Wrong input type "' + (callback !== null ? callback.constructor.name : 'null') + '" for callback function');
    }
    // Using the raw multi command is only possible with this function
    // If the command is not yet added to the client, the internal function should be called right away
    // Otherwise we need to redirect the calls to make sure the interal functions don't get skipped
    // The internal functions could actually be used for any non hooked function
    // but this might change from time to time and at the moment there's no good way to distinguishe them
    // from each other, so let's just do it do it this way for the time being
    if (command === 'multi' || typeof this[command] !== 'function') {
        return this.internal_send_command(new Command(command, args, callback));
    }
    if (typeof callback === 'function') {
        args = args.concat([callback]); // Prevent manipulating the input array
    }
    return this[command].apply(this, args);
};
RedisClient.prototype.end = function (flush) {
    // Flush queue if wanted
    if (flush) {
        this.flush_and_error({
            message: 'Connection forcefully ended and command aborted.',
            code: 'NR_CLOSED'
        });
    } else if (arguments.length === 0) {
        this.warn(
            'Using .end() without the flush parameter is deprecated and throws from v.3.0.0 on.\n' +
            'Please check the doku (https://github.com/NodeRedis/node_redis) and explictly use flush.'
        );
    }
    // Clear retry_timer
    if (this.retry_timer) {
        clearTimeout(this.retry_timer);
        this.retry_timer = null;
    }
    this.stream.removeAllListeners();
    this.stream.on('error', noop);
    this.connected = false;
    this.ready = false;
    this.closing = true;
    return this.stream.destroySoon();
};
RedisClient.prototype.unref = function () {
    if (this.connected) {
        debug("Unref'ing the socket connection");
        this.stream.unref();
    } else {
        debug('Not connected yet, will unref later');
        this.once('connect', function () {
            this.unref();
        });
    }
};
RedisClient.prototype.duplicate = function (options, callback) {
    if (typeof options === 'function') {
        callback = options;
        options = null;
    }
    var existing_options = utils.clone(this.options);
    options = utils.clone(options);
    for (var elem in options) {
        existing_options[elem] = options[elem];
    }
    var client = new RedisClient(existing_options);
    client.selected_db = this.selected_db;
    if (typeof callback === 'function') {
        var ready_listener = function () {
            callback(null, client);
            client.removeAllListeners(error_listener);
        };
        var error_listener = function (err) {
            callback(err);
            client.end(true);
        };
        client.once('ready', ready_listener);
        client.once('error', error_listener);
        return;
    }
    return client;
};

+ 617 - 0
src/doctor/node_modules/redis/lib/individualCommands.js

@ -0,0 +1,617 @@
'use strict';
var utils = require('./utils');
var debug = require('./debug');
var Multi = require('./multi');
var Command = require('./command');
var no_password_is_set = /no password is set/;
var loading = /LOADING/;
var RedisClient = require('../').RedisClient;
/********************************************************************************************
 Replace built-in redis functions
 The callback may be hooked as needed. The same does not apply to the rest of the function.
 State should not be set outside of the callback if not absolutly necessary.
 This is important to make sure it works the same as single command or in a multi context.
 To make sure everything works with the offline queue use the "call_on_write" function.
 This is going to be executed while writing to the stream.
 TODO: Implement individal command generation as soon as possible to prevent divergent code
 on single and multi calls!
********************************************************************************************/
RedisClient.prototype.multi = RedisClient.prototype.MULTI = function multi (args) {
    var multi = new Multi(this, args);
    multi.exec = multi.EXEC = multi.exec_transaction;
    return multi;
};
// ATTENTION: This is not a native function but is still handled as a individual command as it behaves just the same as multi
RedisClient.prototype.batch = RedisClient.prototype.BATCH = function batch (args) {
    return new Multi(this, args);
};
function select_callback (self, db, callback) {
    return function (err, res) {
        if (err === null) {
            // Store db in this.select_db to restore it on reconnect
            self.selected_db = db;
        }
        utils.callback_or_emit(self, callback, err, res);
    };
}
RedisClient.prototype.select = RedisClient.prototype.SELECT = function select (db, callback) {
    return this.internal_send_command(new Command('select', [db], select_callback(this, db, callback)));
};
Multi.prototype.select = Multi.prototype.SELECT = function select (db, callback) {
    this.queue.push(new Command('select', [db], select_callback(this._client, db, callback)));
    return this;
};
RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function monitor (callback) {
    // Use a individual command, as this is a special case that does not has to be checked for any other command
    var self = this;
    var call_on_write = function () {
        // Activating monitor mode has to happen before Redis returned the callback. The monitor result is returned first.
        // Therefore we expect the command to be properly processed. If this is not the case, it's not an issue either.
        self.monitoring = true;
    };
    return this.internal_send_command(new Command('monitor', [], callback, call_on_write));
};
// Only works with batch, not in a transaction
Multi.prototype.monitor = Multi.prototype.MONITOR = function monitor (callback) {
    // Use a individual command, as this is a special case that does not has to be checked for any other command
    if (this.exec !== this.exec_transaction) {
        var self = this;
        var call_on_write = function () {
            self._client.monitoring = true;
        };
        this.queue.push(new Command('monitor', [], callback, call_on_write));
        return this;
    }
    // Set multi monitoring to indicate the exec that it should abort
    // Remove this "hack" as soon as Redis might fix this
    this.monitoring = true;
    return this;
};
function quit_callback (self, callback) {
    return function (err, res) {
        if (err && err.code === 'NR_CLOSED') {
            // Pretent the quit command worked properly in this case.
            // Either the quit landed in the offline queue and was flushed at the reconnect
            // or the offline queue is deactivated and the command was rejected right away
            // or the stream is not writable
            // or while sending the quit, the connection ended / closed
            err = null;
            res = 'OK';
        }
        utils.callback_or_emit(self, callback, err, res);
        if (self.stream.writable) {
            // If the socket is still alive, kill it. This could happen if quit got a NR_CLOSED error code
            self.stream.destroy();
        }
    };
}
RedisClient.prototype.QUIT = RedisClient.prototype.quit = function quit (callback) {
    // TODO: Consider this for v.3
    // Allow the quit command to be fired as soon as possible to prevent it landing in the offline queue.
    // this.ready = this.offline_queue.length === 0;
    var backpressure_indicator = this.internal_send_command(new Command('quit', [], quit_callback(this, callback)));
    // Calling quit should always end the connection, no matter if there's a connection or not
    this.closing = true;
    this.ready = false;
    return backpressure_indicator;
};
// Only works with batch, not in a transaction
Multi.prototype.QUIT = Multi.prototype.quit = function quit (callback) {
    var self = this._client;
    var call_on_write = function () {
        // If called in a multi context, we expect redis is available
        self.closing = true;
        self.ready = false;
    };
    this.queue.push(new Command('quit', [], quit_callback(self, callback), call_on_write));
    return this;
};
function info_callback (self, callback) {
    return function (err, res) {
        if (res) {
            var obj = {};
            var lines = res.toString().split('\r\n');
            var line, parts, sub_parts;
            for (var i = 0; i < lines.length; i++) {
                parts = lines[i].split(':');
                if (parts[1]) {
                    if (parts[0].indexOf('db') === 0) {
                        sub_parts = parts[1].split(',');
                        obj[parts[0]] = {};
                        while (line = sub_parts.pop()) {
                            line = line.split('=');
                            obj[parts[0]][line[0]] = +line[1];
                        }
                    } else {
                        obj[parts[0]] = parts[1];
                    }
                }
            }
            obj.versions = [];
            if (obj.redis_version) {
                obj.redis_version.split('.').forEach(function (num) {
                    obj.versions.push(+num);
                });
            }
            // Expose info key/vals to users
            self.server_info = obj;
        } else {
            self.server_info = {};
        }
        utils.callback_or_emit(self, callback, err, res);
    };
}
// Store info in this.server_info after each call
RedisClient.prototype.info = RedisClient.prototype.INFO = function info (section, callback) {
    var args = [];
    if (typeof section === 'function') {
        callback = section;
    } else if (section !== undefined) {
        args = Array.isArray(section) ? section : [section];
    }
    return this.internal_send_command(new Command('info', args, info_callback(this, callback)));
};
Multi.prototype.info = Multi.prototype.INFO = function info (section, callback) {
    var args = [];
    if (typeof section === 'function') {
        callback = section;
    } else if (section !== undefined) {
        args = Array.isArray(section) ? section : [section];
    }
    this.queue.push(new Command('info', args, info_callback(this._client, callback)));
    return this;
};
function auth_callback (self, pass, callback) {
    return function (err, res) {
        if (err) {
            if (no_password_is_set.test(err.message)) {
                self.warn('Warning: Redis server does not require a password, but a password was supplied.');
                err = null;
                res = 'OK';
            } else if (loading.test(err.message)) {
                // If redis is still loading the db, it will not authenticate and everything else will fail
                debug('Redis still loading, trying to authenticate later');
                setTimeout(function () {
                    self.auth(pass, callback);
                }, 100);
                return;
            }
        }
        utils.callback_or_emit(self, callback, err, res);
    };
}
RedisClient.prototype.auth = RedisClient.prototype.AUTH = function auth (pass, callback) {
    debug('Sending auth to ' + this.address + ' id ' + this.connection_id);
    // Stash auth for connect and reconnect.
    this.auth_pass = pass;
    var ready = this.ready;
    this.ready = ready || this.offline_queue.length === 0;
    var tmp = this.internal_send_command(new Command('auth', [pass], auth_callback(this, pass, callback)));
    this.ready = ready;
    return tmp;
};
// Only works with batch, not in a transaction
Multi.prototype.auth = Multi.prototype.AUTH = function auth (pass, callback) {
    debug('Sending auth to ' + this.address + ' id ' + this.connection_id);
    // Stash auth for connect and reconnect.
    this.auth_pass = pass;
    this.queue.push(new Command('auth', [pass], auth_callback(this._client, callback)));
    return this;
};
RedisClient.prototype.client = RedisClient.prototype.CLIENT = function client () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else if (Array.isArray(arguments[1])) {
        if (len === 3) {
            callback = arguments[2];
        }
        len = arguments[1].length;
        arr = new Array(len + 1);
        arr[0] = arguments[0];
        for (; i < len; i += 1) {
            arr[i + 1] = arguments[1][i];
        }
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    var self = this;
    var call_on_write = undefined;
    // CLIENT REPLY ON|OFF|SKIP
    /* istanbul ignore next: TODO: Remove this as soon as Travis runs Redis 3.2 */
    if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') {
        var reply_on_off = arr[1].toString().toUpperCase();
        if (reply_on_off === 'ON' || reply_on_off === 'OFF' || reply_on_off === 'SKIP') {
            call_on_write = function () {
                self.reply = reply_on_off;
            };
        }
    }
    return this.internal_send_command(new Command('client', arr, callback, call_on_write));
};
Multi.prototype.client = Multi.prototype.CLIENT = function client () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else if (Array.isArray(arguments[1])) {
        if (len === 3) {
            callback = arguments[2];
        }
        len = arguments[1].length;
        arr = new Array(len + 1);
        arr[0] = arguments[0];
        for (; i < len; i += 1) {
            arr[i + 1] = arguments[1][i];
        }
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    var self = this._client;
    var call_on_write = undefined;
    // CLIENT REPLY ON|OFF|SKIP
    /* istanbul ignore next: TODO: Remove this as soon as Travis runs Redis 3.2 */
    if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') {
        var reply_on_off = arr[1].toString().toUpperCase();
        if (reply_on_off === 'ON' || reply_on_off === 'OFF' || reply_on_off === 'SKIP') {
            call_on_write = function () {
                self.reply = reply_on_off;
            };
        }
    }
    this.queue.push(new Command('client', arr, callback, call_on_write));
    return this;
};
RedisClient.prototype.hmset = RedisClient.prototype.HMSET = function hmset () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else if (Array.isArray(arguments[1])) {
        if (len === 3) {
            callback = arguments[2];
        }
        len = arguments[1].length;
        arr = new Array(len + 1);
        arr[0] = arguments[0];
        for (; i < len; i += 1) {
            arr[i + 1] = arguments[1][i];
        }
    } else if (typeof arguments[1] === 'object' && (arguments.length === 2 || arguments.length === 3 && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined'))) {
        arr = [arguments[0]];
        for (var field in arguments[1]) {
            arr.push(field, arguments[1][field]);
        }
        callback = arguments[2];
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    return this.internal_send_command(new Command('hmset', arr, callback));
};
Multi.prototype.hmset = Multi.prototype.HMSET = function hmset () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else if (Array.isArray(arguments[1])) {
        if (len === 3) {
            callback = arguments[2];
        }
        len = arguments[1].length;
        arr = new Array(len + 1);
        arr[0] = arguments[0];
        for (; i < len; i += 1) {
            arr[i + 1] = arguments[1][i];
        }
    } else if (typeof arguments[1] === 'object' && (arguments.length === 2 || arguments.length === 3 && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined'))) {
        arr = [arguments[0]];
        for (var field in arguments[1]) {
            arr.push(field, arguments[1][field]);
        }
        callback = arguments[2];
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    this.queue.push(new Command('hmset', arr, callback));
    return this;
};
RedisClient.prototype.subscribe = RedisClient.prototype.SUBSCRIBE = function subscribe () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    var self = this;
    var call_on_write = function () {
        self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
    };
    return this.internal_send_command(new Command('subscribe', arr, callback, call_on_write));
};
Multi.prototype.subscribe = Multi.prototype.SUBSCRIBE = function subscribe () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    var self = this._client;
    var call_on_write = function () {
        self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
    };
    this.queue.push(new Command('subscribe', arr, callback, call_on_write));
    return this;
};
RedisClient.prototype.unsubscribe = RedisClient.prototype.UNSUBSCRIBE = function unsubscribe () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    var self = this;
    var call_on_write = function () {
        // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
        self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
    };
    return this.internal_send_command(new Command('unsubscribe', arr, callback, call_on_write));
};
Multi.prototype.unsubscribe = Multi.prototype.UNSUBSCRIBE = function unsubscribe () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    var self = this._client;
    var call_on_write = function () {
        // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
        self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
    };
    this.queue.push(new Command('unsubscribe', arr, callback, call_on_write));
    return this;
};
RedisClient.prototype.psubscribe = RedisClient.prototype.PSUBSCRIBE = function psubscribe () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    var self = this;
    var call_on_write = function () {
        self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
    };
    return this.internal_send_command(new Command('psubscribe', arr, callback, call_on_write));
};
Multi.prototype.psubscribe = Multi.prototype.PSUBSCRIBE = function psubscribe () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    var self = this._client;
    var call_on_write = function () {
        self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
    };
    this.queue.push(new Command('psubscribe', arr, callback, call_on_write));
    return this;
};
RedisClient.prototype.punsubscribe = RedisClient.prototype.PUNSUBSCRIBE = function punsubscribe () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    var self = this;
    var call_on_write = function () {
        // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
        self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
    };
    return this.internal_send_command(new Command('punsubscribe', arr, callback, call_on_write));
};
Multi.prototype.punsubscribe = Multi.prototype.PUNSUBSCRIBE = function punsubscribe () {
    var arr,
        len = arguments.length,
        callback,
        i = 0;
    if (Array.isArray(arguments[0])) {
        arr = arguments[0];
        callback = arguments[1];
    } else {
        len = arguments.length;
        // The later should not be the average use case
        if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
            len--;
            callback = arguments[len];
        }
        arr = new Array(len);
        for (; i < len; i += 1) {
            arr[i] = arguments[i];
        }
    }
    var self = this._client;
    var call_on_write = function () {
        // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
        self.pub_sub_mode = self.pub_sub_mode || self.command_queue.length + 1;
    };
    this.queue.push(new Command('punsubscribe', arr, callback, call_on_write));
    return this;
};

+ 187 - 0
src/doctor/node_modules/redis/lib/multi.js

@ -0,0 +1,187 @@
'use strict';
var Queue = require('double-ended-queue');
var utils = require('./utils');
var Command = require('./command');
function Multi (client, args) {
    this._client = client;
    this.queue = new Queue();
    var command, tmp_args;
    if (args) { // Either undefined or an array. Fail hard if it's not an array
        for (var i = 0; i < args.length; i++) {
            command = args[i][0];
            tmp_args = args[i].slice(1);
            if (Array.isArray(command)) {
                this[command[0]].apply(this, command.slice(1).concat(tmp_args));
            } else {
                this[command].apply(this, tmp_args);
            }
        }
    }
}
function pipeline_transaction_command (self, command_obj, index) {
    // Queueing is done first, then the commands are executed
    var tmp = command_obj.callback;
    command_obj.callback = function (err, reply) {
        // Ignore the multi command. This is applied by node_redis and the user does not benefit by it
        if (err && index !== -1) {
            if (tmp) {
                tmp(err);
            }
            err.position = index;
            self.errors.push(err);
        }
        // Keep track of who wants buffer responses:
        // By the time the callback is called the command_obj got the buffer_args attribute attached
        self.wants_buffers[index] = command_obj.buffer_args;
        command_obj.callback = tmp;
    };
    self._client.internal_send_command(command_obj);
}
Multi.prototype.exec_atomic = Multi.prototype.EXEC_ATOMIC = Multi.prototype.execAtomic = function exec_atomic (callback) {
    if (this.queue.length < 2) {
        return this.exec_batch(callback);
    }
    return this.exec(callback);
};
function multi_callback (self, err, replies) {
    var i = 0, command_obj;
    if (err) {
        err.errors = self.errors;
        if (self.callback) {
            self.callback(err);
            // Exclude connection errors so that those errors won't be emitted twice
        } else if (err.code !== 'CONNECTION_BROKEN') {
            self._client.emit('error', err);
        }
        return;
    }
    if (replies) {
        while (command_obj = self.queue.shift()) {
            if (replies[i] instanceof Error) {
                var match = replies[i].message.match(utils.err_code);
                // LUA script could return user errors that don't behave like all other errors!
                if (match) {
                    replies[i].code = match[1];
                }
                replies[i].command = command_obj.command.toUpperCase();
                if (typeof command_obj.callback === 'function') {
                    command_obj.callback(replies[i]);
                }
            } else {
                // If we asked for strings, even in detect_buffers mode, then return strings:
                replies[i] = self._client.handle_reply(replies[i], command_obj.command, self.wants_buffers[i]);
                if (typeof command_obj.callback === 'function') {
                    command_obj.callback(null, replies[i]);
                }
            }
            i++;
        }
    }
    if (self.callback) {
        self.callback(null, replies);
    }
}
Multi.prototype.exec_transaction = function exec_transaction (callback) {
    if (this.monitoring || this._client.monitoring) {
        var err = new RangeError(
            'Using transaction with a client that is in monitor mode does not work due to faulty return values of Redis.'
        );
        err.command = 'EXEC';
        err.code = 'EXECABORT';
        return utils.reply_in_order(this._client, callback, err);
    }
    var self = this;
    var len = self.queue.length;
    self.errors = [];
    self.callback = callback;
    self._client.cork();
    self.wants_buffers = new Array(len);
    pipeline_transaction_command(self, new Command('multi', []), -1);
    // Drain queue, callback will catch 'QUEUED' or error
    for (var index = 0; index < len; index++) {
        // The commands may not be shifted off, since they are needed in the result handler
        pipeline_transaction_command(self, self.queue.get(index), index);
    }
    self._client.internal_send_command(new Command('exec', [], function (err, replies) {
        multi_callback(self, err, replies);
    }));
    self._client.uncork();
    return !self._client.should_buffer;
};
function batch_callback (self, cb, i) {
    return function batch_callback (err, res) {
        if (err) {
            self.results[i] = err;
            // Add the position to the error
            self.results[i].position = i;
        } else {
            self.results[i] = res;
        }
        cb(err, res);
    };
}
Multi.prototype.exec = Multi.prototype.EXEC = Multi.prototype.exec_batch = function exec_batch (callback) {
    var self = this;
    var len = self.queue.length;
    var index = 0;
    var command_obj;
    if (len === 0) {
        utils.reply_in_order(self._client, callback, null, []);
        return !self._client.should_buffer;
    }
    self._client.cork();
    if (!callback) {
        while (command_obj = self.queue.shift()) {
            self._client.internal_send_command(command_obj);
        }
        self._client.uncork();
        return !self._client.should_buffer;
    }
    var callback_without_own_cb = function (err, res) {
        if (err) {
            self.results.push(err);
            // Add the position to the error
            var i = self.results.length - 1;
            self.results[i].position = i;
        } else {
            self.results.push(res);
        }
        // Do not emit an error here. Otherwise each error would result in one emit.
        // The errors will be returned in the result anyway
    };
    var last_callback = function (cb) {
        return function (err, res) {
            cb(err, res);
            callback(null, self.results);
        };
    };
    self.results = [];
    while (command_obj = self.queue.shift()) {
        if (typeof command_obj.callback === 'function') {
            command_obj.callback = batch_callback(self, command_obj.callback, index);
        } else {
            command_obj.callback = callback_without_own_cb;
        }
        if (typeof callback === 'function' && index === len - 1) {
            command_obj.callback = last_callback(command_obj.callback);
        }
        this._client.internal_send_command(command_obj);
        index++;
    }
    self._client.uncork();
    return !self._client.should_buffer;
};
module.exports = Multi;

+ 0 - 46
src/doctor/node_modules/redis/lib/parser/hiredis.js

@ -1,46 +0,0 @@
var events = require("events"),
    util = require("../util"),
    hiredis = require("hiredis");
exports.debug_mode = false;
exports.name = "hiredis";
function HiredisReplyParser(options) {
    this.name = exports.name;
    this.options = options || {};
    this.reset();
    events.EventEmitter.call(this);
}
util.inherits(HiredisReplyParser, events.EventEmitter);
exports.Parser = HiredisReplyParser;
HiredisReplyParser.prototype.reset = function () {
    this.reader = new hiredis.Reader({
        return_buffers: this.options.return_buffers || false
    });
};
HiredisReplyParser.prototype.execute = function (data) {
    var reply;
    this.reader.feed(data);
    while (true) {
        try {
            reply = this.reader.get();
        } catch (err) {
            this.emit("error", err);
            break;
        }
        if (reply === undefined) {
            break;
        }
        if (reply && reply.constructor === Error) {
            this.emit("reply error", reply);
        } else {
            this.emit("reply", reply);
        }
    }
};

+ 0 - 301
src/doctor/node_modules/redis/lib/parser/javascript.js

@ -1,301 +0,0 @@
var events = require("events"),
    util   = require("../util");
function Packet(type, size) {
    this.type = type;
    this.size = +size;
}
exports.name = "javascript";
exports.debug_mode = false;
function ReplyParser(options) {
    this.name = exports.name;
    this.options = options || { };
    this._buffer            = null;
    this._offset            = 0;
    this._encoding          = "utf-8";
    this._debug_mode        = options.debug_mode;
    this._reply_type        = null;
}
util.inherits(ReplyParser, events.EventEmitter);
exports.Parser = ReplyParser;
function IncompleteReadBuffer(message) {
    this.name = "IncompleteReadBuffer";
    this.message = message;
}
util.inherits(IncompleteReadBuffer, Error);
// Buffer.toString() is quite slow for small strings
function small_toString(buf, start, end) {
    var tmp = "", i;
    for (i = start; i < end; i++) {
        tmp += String.fromCharCode(buf[i]);
    }
    return tmp;
}
ReplyParser.prototype._parseResult = function (type) {
    var start, end, offset, packetHeader;
    if (type === 43 || type === 45) { // + or -
        // up to the delimiter
        end = this._packetEndOffset() - 1;
        start = this._offset;
        // include the delimiter
        this._offset = end + 2;
        if (end > this._buffer.length) {
            this._offset = start;
            throw new IncompleteReadBuffer("Wait for more data.");
        }
        if (this.options.return_buffers) {
            return this._buffer.slice(start, end);
        } else {
            if (end - start < 65536) { // completely arbitrary
                return small_toString(this._buffer, start, end);
            } else {
                return this._buffer.toString(this._encoding, start, end);
            }
        }
    } else if (type === 58) { // :
        // up to the delimiter
        end = this._packetEndOffset() - 1;
        start = this._offset;
        // include the delimiter
        this._offset = end + 2;
        if (end > this._buffer.length) {
            this._offset = start;
            throw new IncompleteReadBuffer("Wait for more data.");
        }
        if (this.options.return_buffers) {
            return this._buffer.slice(start, end);
        }
        // return the coerced numeric value
        return +small_toString(this._buffer, start, end);
    } else if (type === 36) { // $
        // set a rewind point, as the packet could be larger than the
        // buffer in memory
        offset = this._offset - 1;
        packetHeader = new Packet(type, this.parseHeader());
        // packets with a size of -1 are considered null
        if (packetHeader.size === -1) {
            return undefined;
        }
        end = this._offset + packetHeader.size;
        start = this._offset;
        // set the offset to after the delimiter
        this._offset = end + 2;
        if (end > this._buffer.length) {
            this._offset = offset;
            throw new IncompleteReadBuffer("Wait for more data.");
        }
        if (this.options.return_buffers) {
            return this._buffer.slice(start, end);
        } else {
            return this._buffer.toString(this._encoding, start, end);
        }
    } else if (type === 42) { // *
        offset = this._offset;
        packetHeader = new Packet(type, this.parseHeader());
        if (packetHeader.size < 0) {
            return null;
        }
        if (packetHeader.size > this._bytesRemaining()) {
            this._offset = offset - 1;
            throw new IncompleteReadBuffer("Wait for more data.");
        }
        var reply = [ ];
        var ntype, i, res;
        offset = this._offset - 1;
        for (i = 0; i < packetHeader.size; i++) {
            ntype = this._buffer[this._offset++];
            if (this._offset > this._buffer.length) {
                throw new IncompleteReadBuffer("Wait for more data.");
            }
            res = this._parseResult(ntype);
            if (res === undefined) {
                res = null;
            }
            reply.push(res);
        }
        return reply;
    }
};
ReplyParser.prototype.execute = function (buffer) {
    this.append(buffer);
    var type, ret, offset;
    while (true) {
        offset = this._offset;
        try {
            // at least 4 bytes: :1\r\n
            if (this._bytesRemaining() < 4) {
                break;
            }
            type = this._buffer[this._offset++];
            if (type === 43) { // +
                ret = this._parseResult(type);
                if (ret === null) {
                    break;
                }
                this.send_reply(ret);
            } else  if (type === 45) { // -
                ret = this._parseResult(type);
                if (ret === null) {
                    break;
                }
                this.send_error(ret);
            } else if (type === 58) { // :
                ret = this._parseResult(type);
                if (ret === null) {
                    break;
                }
                this.send_reply(ret);
            } else if (type === 36) { // $
                ret = this._parseResult(type);
                if (ret === null) {
                    break;
                }
                // check the state for what is the result of
                // a -1, set it back up for a null reply
                if (ret === undefined) {
                    ret = null;
                }
                this.send_reply(ret);
            } else if (type === 42) { // *
                // set a rewind point. if a failure occurs,
                // wait for the next execute()/append() and try again
                offset = this._offset - 1;
                ret = this._parseResult(type);
                this.send_reply(ret);
            }
        } catch (err) {
            // catch the error (not enough data), rewind, and wait
            // for the next packet to appear
            if (! (err instanceof IncompleteReadBuffer)) {
              throw err;
            }
            this._offset = offset;
            break;
        }
    }
};
ReplyParser.prototype.append = function (newBuffer) {
    if (!newBuffer) {
        return;
    }
    // first run
    if (this._buffer === null) {
        this._buffer = newBuffer;
        return;
    }
    // out of data
    if (this._offset >= this._buffer.length) {
        this._buffer = newBuffer;
        this._offset = 0;
        return;
    }
    // very large packet
    // check for concat, if we have it, use it
    if (Buffer.concat !== undefined) {
        this._buffer = Buffer.concat([this._buffer.slice(this._offset), newBuffer]);
    } else {
        var remaining = this._bytesRemaining(),
            newLength = remaining + newBuffer.length,
            tmpBuffer = new Buffer(newLength);
        this._buffer.copy(tmpBuffer, 0, this._offset);
        newBuffer.copy(tmpBuffer, remaining, 0);
        this._buffer = tmpBuffer;
    }
    this._offset = 0;
};
ReplyParser.prototype.parseHeader = function () {
    var end   = this._packetEndOffset(),
        value = small_toString(this._buffer, this._offset, end - 1);
    this._offset = end + 1;
    return value;
};
ReplyParser.prototype._packetEndOffset = function () {
    var offset = this._offset;
    while (this._buffer[offset] !== 0x0d && this._buffer[offset + 1] !== 0x0a) {
        offset++;
        if (offset >= this._buffer.length) {
            throw new IncompleteReadBuffer("didn't see LF after NL reading multi bulk count (" + offset + " => " + this._buffer.length + ", " + this._offset + ")");
        }
    }
    offset++;
    return offset;
};
ReplyParser.prototype._bytesRemaining = function () {
    return (this._buffer.length - this._offset) < 0 ? 0 : (this._buffer.length - this._offset);
};
ReplyParser.prototype.parser_error = function (message) {
    this.emit("error", message);
};
ReplyParser.prototype.send_error = function (reply) {
    this.emit("reply error", reply);
};
ReplyParser.prototype.send_reply = function (reply) {
    this.emit("reply", reply);
};

+ 0 - 59
src/doctor/node_modules/redis/lib/queue.js

@ -1,59 +0,0 @@
// Queue class adapted from Tim Caswell's pattern library
// http://github.com/creationix/pattern/blob/master/lib/pattern/queue.js
function Queue() {
    this.tail = [];
    this.head = [];
    this.offset = 0;
}
Queue.prototype.shift = function () {
    if (this.offset === this.head.length) {
        var tmp = this.head;
        tmp.length = 0;
        this.head = this.tail;
        this.tail = tmp;
        this.offset = 0;
        if (this.head.length === 0) {
            return;
        }
    }
    return this.head[this.offset++]; // sorry, JSLint
};
Queue.prototype.push = function (item) {
    return this.tail.push(item);
};
Queue.prototype.forEach = function (fn, thisv) {
    var array = this.head.slice(this.offset), i, il;
    array.push.apply(array, this.tail);
    if (thisv) {
        for (i = 0, il = array.length; i < il; i += 1) {
            fn.call(thisv, array[i], i, array);
        }
    } else {
        for (i = 0, il = array.length; i < il; i += 1) {
            fn(array[i], i, array);
        }
    }
    return array;
};
Queue.prototype.getLength = function () {
    return this.head.length - this.offset + this.tail.length;
};
    
Object.defineProperty(Queue.prototype, "length", {
    get: function () {
        return this.getLength();
    }
});
if (typeof module !== "undefined" && module.exports) {
    module.exports = Queue;
}

+ 0 - 12
src/doctor/node_modules/redis/lib/to_array.js

@ -1,12 +0,0 @@
function to_array(args) {
    var len = args.length,
        arr = new Array(len), i;
    for (i = 0; i < len; i += 1) {
        arr[i] = args[i];
    }
    return arr;
}
module.exports = to_array;

+ 0 - 11
src/doctor/node_modules/redis/lib/util.js

@ -1,11 +0,0 @@
// Support for very old versions of node where the module was called "sys".  At some point, we should abandon this.
var util;
try {
    util = require("util");
} catch (err) {
    util = require("sys");
}
module.exports = util;

+ 134 - 0
src/doctor/node_modules/redis/lib/utils.js

@ -0,0 +1,134 @@
'use strict';
// hgetall converts its replies to an Object. If the reply is empty, null is returned.
// These function are only called with internal data and have therefore always the same instanceof X
function replyToObject (reply) {
    // The reply might be a string or a buffer if this is called in a transaction (multi)
    if (reply.length === 0 || !(reply instanceof Array)) {
        return null;
    }
    var obj = {};
    for (var i = 0; i < reply.length; i += 2) {
        obj[reply[i].toString('binary')] = reply[i + 1];
    }
    return obj;
}
function replyToStrings (reply) {
    if (reply instanceof Buffer) {
        return reply.toString();
    }
    if (reply instanceof Array) {
        var res = new Array(reply.length);
        for (var i = 0; i < reply.length; i++) {
            // Recusivly call the function as slowlog returns deep nested replies
            res[i] = replyToStrings(reply[i]);
        }
        return res;
    }
    return reply;
}
function print (err, reply) {
    if (err) {
        // A error always begins with Error:
        console.log(err.toString());
    } else {
        console.log('Reply: ' + reply);
    }
}
var camelCase;
// Deep clone arbitrary objects with arrays. Can't handle cyclic structures (results in a range error)
// Any attribute with a non primitive value besides object and array will be passed by reference (e.g. Buffers, Maps, Functions)
// All capital letters are going to be replaced with a lower case letter and a underscore infront of it
function clone (obj) {
    var copy;
    if (Array.isArray(obj)) {
        copy = new Array(obj.length);
        for (var i = 0; i < obj.length; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }
    if (Object.prototype.toString.call(obj) === '[object Object]') {
        copy = {};
        var elems = Object.keys(obj);
        var elem;
        while (elem = elems.pop()) {
            if (elem === 'tls') { // special handle tls
                copy[elem] = obj[elem];
                continue;
            }
            // Accept camelCase options and convert them to snake_case
            var snake_case = elem.replace(/[A-Z][^A-Z]/g, '_$&').toLowerCase();
            // If camelCase is detected, pass it to the client, so all variables are going to be camelCased
            // There are no deep nested options objects yet, but let's handle this future proof
            if (snake_case !== elem.toLowerCase()) {
                camelCase = true;
            }
            copy[snake_case] = clone(obj[elem]);
        }
        return copy;
    }
    return obj;
}
function convenienceClone (obj) {
    camelCase = false;
    obj = clone(obj) || {};
    if (camelCase) {
        obj.camel_case = true;
    }
    return obj;
}
function callbackOrEmit (self, callback, err, res) {
    if (callback) {
        callback(err, res);
    } else if (err) {
        self.emit('error', err);
    }
}
function replyInOrder (self, callback, err, res, queue) {
    // If the queue is explicitly passed, use that, otherwise fall back to the offline queue first,
    // as there might be commands in both queues at the same time
    var command_obj;
    /* istanbul ignore if: TODO: Remove this as soon as we test Redis 3.2 on travis */
    if (queue) {
        command_obj = queue.peekBack();
    } else {
        command_obj = self.offline_queue.peekBack() || self.command_queue.peekBack();
    }
    if (!command_obj) {
        process.nextTick(function () {
            callbackOrEmit(self, callback, err, res);
        });
    } else {
        var tmp = command_obj.callback;
        command_obj.callback = tmp ?
            function (e, r) {
                tmp(e, r);
                callbackOrEmit(self, callback, err, res);
            } :
            function (e, r) {
                if (e) {
                    self.emit('error', e);
                }
                callbackOrEmit(self, callback, err, res);
            };
    }
}
module.exports = {
    reply_to_strings: replyToStrings,
    reply_to_object: replyToObject,
    print: print,
    err_code: /^([A-Z]+)\s+(.+)$/,
    monitor_regex: /^[0-9]{10,11}\.[0-9]+ \[[0-9]{1,3} [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]{1,5}\].*/,
    clone: convenienceClone,
    callback_or_emit: callbackOrEmit,
    reply_in_order: replyInOrder
};

+ 31 - 0
src/doctor/node_modules/redis/node_modules/double-ended-queue/.npmignore

@ -0,0 +1,31 @@
node_modules/*
todo.txt
npm-debug.log
test/*
benchmark/*
browser/*
src/*
async
sync
mixed
bench.json
js/browser
js/browser/*
js/debug
js/debug/*
reader.js
read.txt
bench
.editorconfig
.jshintrc
ast_passes.js
mocharun.js
throwaway.js
throwaway.html
deque.sublime-workspace
deque.sublime-project
changelog.js
.travis.yml
sauce_connect.log
nodex64.exe
bump.js

+ 188 - 0
src/doctor/node_modules/redis/node_modules/double-ended-queue/Gruntfile.js

@ -0,0 +1,188 @@
"use strict";
Error.stackTraceLimit = 100;
var astPasses = require("./ast_passes.js");
module.exports = function( grunt ) {
    var isCI = !!grunt.option("ci");
    var license;
    function getLicense() {
        if( !license ) {
            var fs = require("fs");
            var text = fs.readFileSync("LICENSE", "utf8");
            text = text.split("\n").map(function(line, index){
                return " * " + line;
            }).join("\n")
            license = "/**\n" + text + "\n */\n";
        }
        return license
    }
    function writeFile( dest, content ) {
        grunt.file.write( dest, content );
        grunt.log.writeln('File "' + dest + '" created.');
    }
    var gruntConfig = {};
    var getGlobals = function() {
        var fs = require("fs");
        var file = "./src/constants.js";
        var contents = fs.readFileSync(file, "utf8");
        var rconstantname = /CONSTANT\(\s*([^,]+)/g;
        var m;
        var globals = {
            "console": false,
            "require": false,
            "module": false,
            "define": false
        };
        while( ( m = rconstantname.exec( contents ) ) ) {
            globals[m[1]] = false;
        }
        return globals;
    }
    gruntConfig.pkg = grunt.file.readJSON("package.json");
    gruntConfig.jshint = {
        all: {
            options: {
                globals: getGlobals(),
                "bitwise": false,
                "camelcase": true,
                "curly": true,
                "eqeqeq": true,
                "es3": true,
                "forin": true,
                "immed": true,
                "latedef": false,
                "newcap": true,
                "noarg": true,
                "noempty": true,
                "nonew": true,
                "plusplus": false,
                "quotmark": "double",
                "undef": true,
                "unused": true,
                "strict": false,
                "trailing": true,
                "maxparams": 7,
                "maxlen": 80,
                "asi": false,
                "boss": true,
                "eqnull": true,
                "evil": true,
                "expr": false,
                "funcscope": false,
                "globalstrict": false,
                "lastsemic": false,
                "laxcomma": false,
                "laxbreak": false,
                "loopfunc": true,
                "multistr": true,
                "proto": false,
                "scripturl": true,
                "smarttabs": false,
                "shadow": true,
                "sub": true,
                "supernew": false,
                "validthis": true,
                "browser": true,
                "jquery": true,
                "devel": true,
                '-W014': true,
                '-W116': true,
                '-W106': true,
                '-W064': true,
                '-W097': true
            },
            files: {
                src: [
                    "./src/deque.js"
                ]
            }
        }
    };
    if( !isCI ) {
        gruntConfig.jshint.all.options.reporter = require("jshint-stylish");
    }
    gruntConfig.bump = {
      options: {
        files: ['package.json'],
        updateConfigs: [],
        commit: true,
        commitMessage: 'Release v%VERSION%',
        commitFiles: ['-a'],
        createTag: true,
        tagName: 'v%VERSION%',
        tagMessage: 'Version %VERSION%',
        false: true,
        pushTo: 'master',
        gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe'
      }
    };
    grunt.initConfig(gruntConfig);
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-bump');
    grunt.registerTask( "build", function() {
        var fs = require("fs");
        var CONSTANTS_FILE = "./src/constants.js";
        astPasses.readConstants(fs.readFileSync(CONSTANTS_FILE, "utf8"), CONSTANTS_FILE);
        var fileNames = ["deque.js"];
        fileNames.forEach(function(fileName){
            var src = fs.readFileSync("./src/" + fileName, "utf8");
            src = astPasses.removeComments(src, fileName);
            src = astPasses.expandConstants(src, fileName);
            src = getLicense() + src;
            writeFile("./js/" + fileName, src);
        });
    });
    grunt.registerTask( "testrun", function() {
        var fs = require("fs");
        var done = this.async();
        var Mocha = require("mocha");
        var mochaOpts = {
            reporter: "spec",
            timeout: 500,
            slow: Infinity
        };
        var mocha = new Mocha(mochaOpts);
        fs.readdirSync("./test").forEach(function(fileName) {
            mocha.addFile("./test/" + fileName);
        });
        mocha.run(function(err){
            if( err ) {
                process.stderr.write(test.title + "\n" + err.stack + "\n");
                done(err);
            }
            else {
                done();
            }
        }).on( "fail", function( test, err ) {
            process.stderr.write(test.title + "\n" + err.stack + "\n");
            done(err);
        });
    });
    grunt.registerTask( "test", ["jshint", "build", "testrun"] );
    grunt.registerTask( "default", ["jshint", "build"] );
};

+ 19 - 0
src/doctor/node_modules/redis/node_modules/double-ended-queue/LICENSE

@ -0,0 +1,19 @@
Copyright (c) 2013 Petka Antonov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:</p>
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

+ 293 - 0
src/doctor/node_modules/redis/node_modules/double-ended-queue/README.md

@ -0,0 +1,293 @@
#Introduction
Extremely fast [double-ended queue](http://en.wikipedia.org/wiki/Double-ended_queue) implementation. Double-ended queue can also be used as a:
- [Stack](http://en.wikipedia.org/wiki/Stack_\(abstract_data_type\))
- [Queue](http://en.wikipedia.org/wiki/Queue_\(data_structure\))
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).
Every queue operation is done in constant `O(1)` - including random access from `.get()`.
#Topics
- [Quick start](#quick-start)
- [Why not use an Array?](#why-not-use-an-array)
- [Using double-ended queue as a normal queue](#using-double-ended-queue-as-a-normal-queue)
- [API reference and examples](#api)
- [Performance](#performance)
#Quick start
    npm install double-ended-queue
```js
var Deque = require("double-ended-queue");
var deque = new Deque([1,2,3,4]);
deque.shift(); //1
deque.pop(); //4
```
#Why not use an Array?
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.
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.
But arrays use "native" methods, they must be faster!
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.
#Using double-ended queue as a normal queue
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.
#API
- [`new Deque()`](#new-deque---deque)
- [`new Deque(Array items)`](#new-dequearray-items---deque)
- [`new Deque(int capacity)`](#new-dequeint-capacity---deque)
- [`push(dynamic items...)`](#pushdynamic-items---int)
- [`unshift(dynamic items...)`](#unshiftdynamic-items---int)
- [`pop()`](#pop---dynamic)
- [`shift()`](#shift---dynamic)
- [`toArray()`](#toarray---array)
- [`peekBack()`](#peekback---dynamic)
- [`peekFront()`](#peekfront---dynamic)
- [`get(int index)`](#getint-index---dynamic)
- [`isEmpty()`](#isempty---boolean)
- [`clear()`](#clear---void)
#####`new Deque()` -> `Deque`
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).
```js
var deque = new Deque();
deque.push(1, 2, 3);
deque.shift(); //1
deque.pop(); //3
```
<hr>
#####`new Deque(Array items)` -> `Deque`
Creates a double-ended queue from `items`.
```js
var deque = new Deque([1,2,3,4]);
deque.shift(); //1
deque.pop(); //4
```
<hr>
#####`new Deque(int capacity)` -> `Deque`
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.
The reason to give an initial capacity is to avoid potentially expensive resizing operations at runtime.
```js
var deque = new Deque(100);
deque.push(1, 2, 3);
deque.shift(); //1
deque.pop(); //3
```
<hr>
#####`push(dynamic items...)` -> `int`
Push items to the back of this queue. Returns the amount of items currently in the queue after the operation.
```js
var deque = new Deque();
deque.push(1);
deque.pop(); //1
deque.push(1, 2, 3);
deque.shift(); //1
deque.shift(); //2
deque.shift(); //3
```
**Aliases:** `enqueue`, `insertBack`
<hr>
#####`unshift(dynamic items...)` -> `int`
Unshift items to the front of this queue. Returns the amount of items currently in the queue after the operation.
```js
var deque = new Deque([2,3]);
deque.unshift(1);
deque.toString(); //"1,2,3"
deque.unshift(-2, -1, 0);
deque.toString(); //"-2,-1,0,1,2,3"
```
**Aliases:** `insertFront`
<hr>
#####`pop()` -> `dynamic`
Pop off the item at the back of this queue.
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).
If the queue is empty, `undefined` is returned. If you need to differentiate between `undefined` values in the queue and `pop()` return value -
check the queue `.length` before popping.
```js
var deque = new Deque([1,2,3]);
deque.pop(); //3
deque.pop(); //2
deque.pop(); //1
deque.pop(); //undefined
```
**Aliases:** `removeBack`
<hr>
#####`shift()` -> `dynamic`
Shifts off the item at the front of this queue.
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).
If the queue is empty, `undefined` is returned. If you need to differentiate between `undefined` values in the queue and `shift()` return value -
check the queue `.length` before shifting.
```js
var deque = new Deque([1,2,3]);
deque.shift(); //1
deque.shift(); //2
deque.shift(); //3
deque.shift(); //undefined
```
**Aliases:** `removeFront`, `dequeue`
<hr>
#####`toArray()` -> `Array`
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.
```js
var deque = new Deque([1,2,3]);
deque.push(4);
deque.unshift(0);
deque.toArray(); //[0,1,2,3,4]
```
**Aliases:** `toJSON`
<hr>
#####`peekBack()` -> `dynamic`
Returns the item that is at the back of this queue without removing it.
If the queue is empty, `undefined` is returned.
```js
var deque = new Deque([1,2,3]);
deque.push(4);
deque.peekBack(); //4
```
<hr>
#####`peekFront()` -> `dynamic`
Returns the item that is at the front of this queue without removing it.
If the queue is empty, `undefined` is returned.
```js
var deque = new Deque([1,2,3]);
deque.push(4);
deque.peekFront(); //1
```
<hr>
#####`get(int index)` -> `dynamic`
Returns the item that is at the given `index` of this queue without removing it.
The index is zero-based, so `.get(0)` will return the item that is at the front, `.get(1)` will return
the item that comes after and so on.
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,
`.get(-2)` will return the item that comes before and so on.
Returns `undefined` if `index` is not a valid index into the queue.
```js
var deque = new Deque([1,2,3]);
deque.get(0); //1
deque.get(1); //2
deque.get(2); //3
deque.get(-1); //3
deque.get(-2); //2
deque.get(-3); //1
```
**Note**: Even though indexed accessor (e.g. `queue[0]`) could *appear* to return a correct value *sometimes*, this is completely unreliable. The numeric slots
of the deque object are internally used as an optimization and have no meaningful order or meaning to outside. Always use `.get()`.
**Note**: The implementation has O(1) random access using `.get()`.
<hr>
#####`isEmpty()` -> `boolean`
Return `true` if this queue is empty, `false` otherwise.
```js
var deque = new Deque();
deque.isEmpty(); //true
deque.push(1);
deque.isEmpty(); //false
```
<hr>
#####`clear()` -> `void`
Remove all items from this queue. Does not change the queue's capacity.
```js
var deque = new Deque([1,2,3]);
deque.toString(); //"1,2,3"
deque.clear();
deque.toString(); //""
```
<hr>
#Performance
Clone the repo and `npm install`. Then run the `bench` script.
##1000 items in the queue
    double-ended-queue x 15,532,714 ops/sec ±0.19% (96 runs sampled)
    built-in array x 6,501,398 ops/sec ±0.87% (95 runs sampled)
    node-deque x 2,938,068 ops/sec ±3.50% (68 runs sampled)
##2 million items in the queue
    double-ended-queue x 14,425,547 ops/sec ±0.17% (94 runs sampled)
    node-deque x 2,815,628 ops/sec ±10.56% (76 runs sampled)
    built-in array x 19.23 ops/sec ±0.35% (51 runs sampled)
Noteworthy is just how bad the degradation can be for built-in array when V8 cannot use the trick.

+ 275 - 0
src/doctor/node_modules/redis/node_modules/double-ended-queue/js/deque.js

@ -0,0 +1,275 @@
/**
 * Copyright (c) 2013 Petka Antonov
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:</p>
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
"use strict";
function Deque(capacity) {
    this._capacity = getCapacity(capacity);
    this._length = 0;
    this._front = 0;
    if (isArray(capacity)) {
        var len = capacity.length;
        for (var i = 0; i < len; ++i) {
            this[i] = capacity[i];
        }
        this._length = len;
    }
}
Deque.prototype.toArray = function Deque$toArray() {
    var len = this._length;
    var ret = new Array(len);
    var front = this._front;
    var capacity = this._capacity;
    for (var j = 0; j < len; ++j) {
        ret[j] = this[(front + j) & (capacity - 1)];
    }
    return ret;
};
Deque.prototype.push = function Deque$push(item) {
    var argsLength = arguments.length;
    var length = this._length;
    if (argsLength > 1) {
        var capacity = this._capacity;
        if (length + argsLength > capacity) {
            for (var i = 0; i < argsLength; ++i) {
                this._checkCapacity(length + 1);
                var j = (this._front + length) & (this._capacity - 1);
                this[j] = arguments[i];
                length++;
                this._length = length;
            }
            return length;
        }
        else {
            var j = this._front;
            for (var i = 0; i < argsLength; ++i) {
                this[(j + length) & (capacity - 1)] = arguments[i];
                j++;
            }
            this._length = length + argsLength;
            return length + argsLength;
        }
    }
    if (argsLength === 0) return length;
    this._checkCapacity(length + 1);
    var i = (this._front + length) & (this._capacity - 1);
    this[i] = item;
    this._length = length + 1;
    return length + 1;
};
Deque.prototype.pop = function Deque$pop() {
    var length = this._length;
    if (length === 0) {
        return void 0;
    }
    var i = (this._front + length - 1) & (this._capacity - 1);
    var ret = this[i];
    this[i] = void 0;
    this._length = length - 1;
    return ret;
};
Deque.prototype.shift = function Deque$shift() {
    var length = this._length;
    if (length === 0) {
        return void 0;
    }
    var front = this._front;
    var ret = this[front];
    this[front] = void 0;
    this._front = (front + 1) & (this._capacity - 1);
    this._length = length - 1;
    return ret;
};
Deque.prototype.unshift = function Deque$unshift(item) {
    var length = this._length;
    var argsLength = arguments.length;
    if (argsLength > 1) {
        var capacity = this._capacity;
        if (length + argsLength > capacity) {
            for (var i = argsLength - 1; i >= 0; i--) {
                this._checkCapacity(length + 1);
                var capacity = this._capacity;
                var j = (((( this._front - 1 ) &
                    ( capacity - 1) ) ^ capacity ) - capacity );
                this[j] = arguments[i];
                length++;
                this._length = length;
                this._front = j;
            }
            return length;
        }
        else {
            var front = this._front;
            for (var i = argsLength - 1; i >= 0; i--) {
                var j = (((( front - 1 ) &
                    ( capacity - 1) ) ^ capacity ) - capacity );
                this[j] = arguments[i];
                front = j;
            }
            this._front = front;
            this._length = length + argsLength;
            return length + argsLength;
        }
    }
    if (argsLength === 0) return length;
    this._checkCapacity(length + 1);
    var capacity = this._capacity;
    var i = (((( this._front - 1 ) &
        ( capacity - 1) ) ^ capacity ) - capacity );
    this[i] = item;
    this._length = length + 1;
    this._front = i;
    return length + 1;
};
Deque.prototype.peekBack = function Deque$peekBack() {
    var length = this._length;
    if (length === 0) {
        return void 0;
    }
    var index = (this._front + length - 1) & (this._capacity - 1);
    return this[index];
};
Deque.prototype.peekFront = function Deque$peekFront() {
    if (this._length === 0) {
        return void 0;
    }
    return this[this._front];
};
Deque.prototype.get = function Deque$get(index) {
    var i = index;
    if ((i !== (i | 0))) {
        return void 0;
    }
    var len = this._length;
    if (i < 0) {
        i = i + len;
    }
    if (i < 0 || i >= len) {
        return void 0;
    }
    return this[(this._front + i) & (this._capacity - 1)];
};
Deque.prototype.isEmpty = function Deque$isEmpty() {
    return this._length === 0;
};
Deque.prototype.clear = function Deque$clear() {
    var len = this._length;
    var front = this._front;
    var capacity = this._capacity;
    for (var j = 0; j < len; ++j) {
        this[(front + j) & (capacity - 1)] = void 0;
    }
    this._length = 0;
    this._front = 0;
};
Deque.prototype.toString = function Deque$toString() {
    return this.toArray().toString();
};
Deque.prototype.valueOf = Deque.prototype.toString;
Deque.prototype.removeFront = Deque.prototype.shift;
Deque.prototype.removeBack = Deque.prototype.pop;
Deque.prototype.insertFront = Deque.prototype.unshift;
Deque.prototype.insertBack = Deque.prototype.push;
Deque.prototype.enqueue = Deque.prototype.push;
Deque.prototype.dequeue = Deque.prototype.shift;
Deque.prototype.toJSON = Deque.prototype.toArray;
Object.defineProperty(Deque.prototype, "length", {
    get: function() {
        return this._length;
    },
    set: function() {
        throw new RangeError("");
    }
});
Deque.prototype._checkCapacity = function Deque$_checkCapacity(size) {
    if (this._capacity < size) {
        this._resizeTo(getCapacity(this._capacity * 1.5 + 16));
    }
};
Deque.prototype._resizeTo = function Deque$_resizeTo(capacity) {
    var oldCapacity = this._capacity;
    this._capacity = capacity;
    var front = this._front;
    var length = this._length;
    if (front + length > oldCapacity) {
        var moveItemsCount = (front + length) & (oldCapacity - 1);
        arrayMove(this, 0, this, oldCapacity, moveItemsCount);
    }
};
var isArray = Array.isArray;
function arrayMove(src, srcIndex, dst, dstIndex, len) {
    for (var j = 0; j < len; ++j) {
        dst[j + dstIndex] = src[j + srcIndex];
        src[j + srcIndex] = void 0;
    }
}
function pow2AtLeast(n) {
    n = n >>> 0;
    n = n - 1;
    n = n | (n >> 1);
    n = n | (n >> 2);
    n = n | (n >> 4);
    n = n | (n >> 8);
    n = n | (n >> 16);
    return n + 1;
}
function getCapacity(capacity) {
    if (typeof capacity !== "number") {
        if (isArray(capacity)) {
            capacity = capacity.length;
        }
        else {
            return 16;
        }
    }
    return pow2AtLeast(
        Math.min(
            Math.max(16, capacity), 1073741824)
    );
}
module.exports = Deque;

+ 69 - 0
src/doctor/node_modules/redis/node_modules/double-ended-queue/package.json

@ -0,0 +1,69 @@
{
  "name": "double-ended-queue",
  "description": "Extremely fast double-ended queue implementation",
  "version": "2.1.0-0",
  "keywords": [
    "data-structure",
    "data-structures",
    "queue",
    "deque",
    "double-ended-queue"
  ],
  "scripts": {
    "test": "grunt test"
  },
  "homepage": "https://github.com/petkaantonov/deque",
  "repository": {
    "type": "git",
    "url": "git://github.com/petkaantonov/deque.git"
  },
  "bugs": {
    "url": "http://github.com/petkaantonov/deque/issues"
  },
  "license": "MIT",
  "author": {
    "name": "Petka Antonov",
    "email": "petka_antonov@hotmail.com",
    "url": "http://github.com/petkaantonov/"
  },
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-jshint": "~0.6.4",
    "jshint-stylish": "latest",
    "acorn": "~0.3.1",
    "mocha": "~1.12.1",
    "grunt-cli": "~0.1.9",
    "bluebird": "~0.11",
    "benchmark": "~1.0.0",
    "deque": "0.0.4",
    "q": "~0.9.7",
    "semver-utils": "~1.1.0"
  },
  "main": "./js/deque.js",
  "gitHead": "51eada75cea686f1eb0c8bb5be486ac630e9b7ee",
  "_id": "double-ended-queue@2.1.0-0",
  "_shasum": "103d3527fd31528f40188130c841efdd78264e5c",
  "_from": "double-ended-queue@>=2.1.0-0 <3.0.0",
  "_npmVersion": "2.1.12",
  "_nodeVersion": "0.10.34",
  "_npmUser": {
    "name": "esailija",
    "email": "petka_antonov@hotmail.com"
  },
  "maintainers": [
    {
      "name": "esailija",
      "email": "petka_antonov@hotmail.com"
    }
  ],
  "dist": {
    "shasum": "103d3527fd31528f40188130c841efdd78264e5c",
    "size": 7140,
    "noattachment": false,
    "tarball": "http://registry.npm.taobao.org/double-ended-queue/download/double-ended-queue-2.1.0-0.tgz"
  },
  "directories": {},
  "publish_time": 1420470723596,
  "_cnpm_publish_time": 1420470723596,
  "_resolved": "https://registry.npm.taobao.org/double-ended-queue/download/double-ended-queue-2.1.0-0.tgz"
}

+ 29 - 0
src/doctor/node_modules/redis/node_modules/redis-commands/.npmignore

@ -0,0 +1,29 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.rdb
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Commenting this out is preferred by some people, see
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
# Users Environment Variables
.lock-wscript

+ 9 - 0
src/doctor/node_modules/redis/node_modules/redis-commands/.travis.yml

@ -0,0 +1,9 @@
language: node_js
sudo: false
node_js:
  - "0.10"
  - "0.12"
  - "4"
  - "5"
after_success:
  - CODECLIMATE_REPO_TOKEN=b57723fafcf0516f275d6b380cd506fd082ea88d86507eb82c8abd489b9b9a09 node ./node_modules/.bin/codeclimate-test-reporter < coverage/lcov.info

+ 22 - 0
src/doctor/node_modules/redis/node_modules/redis-commands/LICENSE

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 NodeRedis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

+ 51 - 0
src/doctor/node_modules/redis/node_modules/redis-commands/README.md

@ -0,0 +1,51 @@
# Redis Commands
[![Build Status](https://travis-ci.org/NodeRedis/redis-commands.png?branch=master)](https://travis-ci.org/NodeRedis/redis-commands)
[![Code Climate](https://codeclimate.com/github/NodeRedis/redis-commands/badges/gpa.svg)](https://codeclimate.com/github/NodeRedis/redis-commands)
[![Test Coverage](https://codeclimate.com/github/NodeRedis/redis-commands/badges/coverage.svg)](https://codeclimate.com/github/NodeRedis/redis-commands/coverage)
This module exports all the commands that Redis supports.
## Install
```shell
$ npm install redis-commands
```
## Usage
```javascript
var commands = require('redis-commands');
```
`.list` is an array contains all the lowercased commands:
```javascript
commands.list.forEach(function (command) {
  console.log(command);
});
```
`.exists()` is used to check if the command exists:
```javascript
commands.exists('set') // true
commands.exists('other-command') // false
```
`.hasFlag()` is used to check if the command has the flag:
```javascript
commands.hasFlag('set', 'readonly') // false
```
`.getKeyIndexes()` is used to get the indexes of keys in the command arguments:
```javascript
commands.getKeyIndexes('set', ['key', 'value']) // [0]
commands.getKeyIndexes('mget', ['key1', 'key2']) // [0, 1]
```
## Acknowledgment
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).

+ 26 - 0
src/doctor/node_modules/redis/node_modules/redis-commands/changelog.md

@ -0,0 +1,26 @@
## v.1.3.0 - 20 Oct, 2016
Features
-  Rebuild the commands with the newest Redis unstable release
## v.1.2.0 - 21 Apr, 2016
Features
-  Added support for `MIGRATE [...] KEYS key1, key2` (Redis >= v.3.0.6)
-  Added build sanity check for unhandled commands with moveable keys
-  Rebuild the commands with the newest unstable release
-  Improved performance of .getKeyIndexes()
Bugfix
-  Fixed command command returning the wrong arity due to a Redis bug
-  Fixed brpop command returning the wrong keystop due to a Redis bug
## v.1.1.0 - 09 Feb, 2016
Features
-  Added .exists() to check for command existence
-  Improved performance of .hasFlag()

+ 1796 - 0
src/doctor/node_modules/redis/node_modules/redis-commands/commands.json

@ -0,0 +1,1796 @@
{
  "append": {
    "arity": 3,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "asking": {
    "arity": 1,
    "flags": [
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "auth": {
    "arity": 2,
    "flags": [
      "noscript",
      "loading",
      "stale",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "bgrewriteaof": {
    "arity": 1,
    "flags": [
      "admin"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "bgsave": {
    "arity": -1,
    "flags": [
      "admin"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "bitcount": {
    "arity": -2,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "bitfield": {
    "arity": -2,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "bitop": {
    "arity": -4,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 2,
    "keyStop": -1,
    "step": 1
  },
  "bitpos": {
    "arity": -3,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "blpop": {
    "arity": -3,
    "flags": [
      "write",
      "noscript"
    ],
    "keyStart": 1,
    "keyStop": -2,
    "step": 1
  },
  "brpop": {
    "arity": -3,
    "flags": [
      "write",
      "noscript"
    ],
    "keyStart": 1,
    "keyStop": -2,
    "step": 1
  },
  "brpoplpush": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom",
      "noscript"
    ],
    "keyStart": 1,
    "keyStop": 2,
    "step": 1
  },
  "client": {
    "arity": -2,
    "flags": [
      "admin",
      "noscript"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "cluster": {
    "arity": -2,
    "flags": [
      "admin"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "command": {
    "arity": 1,
    "flags": [
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "config": {
    "arity": -2,
    "flags": [
      "admin",
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "dbsize": {
    "arity": 1,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "debug": {
    "arity": -1,
    "flags": [
      "admin",
      "noscript"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "decr": {
    "arity": 2,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "decrby": {
    "arity": 3,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "del": {
    "arity": -2,
    "flags": [
      "write"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "discard": {
    "arity": 1,
    "flags": [
      "noscript",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "dump": {
    "arity": 2,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "echo": {
    "arity": 2,
    "flags": [
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "eval": {
    "arity": -3,
    "flags": [
      "noscript",
      "movablekeys"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "evalsha": {
    "arity": -3,
    "flags": [
      "noscript",
      "movablekeys"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "exec": {
    "arity": 1,
    "flags": [
      "noscript",
      "skip_monitor"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "exists": {
    "arity": -2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "expire": {
    "arity": 3,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "expireat": {
    "arity": 3,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "flushall": {
    "arity": -1,
    "flags": [
      "write"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "flushdb": {
    "arity": -1,
    "flags": [
      "write"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "geoadd": {
    "arity": -5,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "geodist": {
    "arity": -4,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "geohash": {
    "arity": -2,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "geopos": {
    "arity": -2,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "georadius": {
    "arity": -6,
    "flags": [
      "write"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "georadiusbymember": {
    "arity": -5,
    "flags": [
      "write"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "get": {
    "arity": 2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "getbit": {
    "arity": 3,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "getrange": {
    "arity": 4,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "getset": {
    "arity": 3,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hdel": {
    "arity": -3,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hexists": {
    "arity": 3,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hget": {
    "arity": 3,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hgetall": {
    "arity": 2,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hincrby": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hincrbyfloat": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hkeys": {
    "arity": 2,
    "flags": [
      "readonly",
      "sort_for_script"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hlen": {
    "arity": 2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hmget": {
    "arity": -3,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hmset": {
    "arity": -4,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "host:": {
    "arity": -1,
    "flags": [
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "hscan": {
    "arity": -3,
    "flags": [
      "readonly",
      "random"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hset": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hsetnx": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hstrlen": {
    "arity": 3,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "hvals": {
    "arity": 2,
    "flags": [
      "readonly",
      "sort_for_script"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "incr": {
    "arity": 2,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "incrby": {
    "arity": 3,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "incrbyfloat": {
    "arity": 3,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "info": {
    "arity": -1,
    "flags": [
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "keys": {
    "arity": 2,
    "flags": [
      "readonly",
      "sort_for_script"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "lastsave": {
    "arity": 1,
    "flags": [
      "random",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "latency": {
    "arity": -2,
    "flags": [
      "admin",
      "noscript",
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "lindex": {
    "arity": 3,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "linsert": {
    "arity": 5,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "llen": {
    "arity": 2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "lpop": {
    "arity": 2,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "lpush": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "lpushx": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "lrange": {
    "arity": 4,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "lrem": {
    "arity": 4,
    "flags": [
      "write"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "lset": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "ltrim": {
    "arity": 4,
    "flags": [
      "write"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "memory": {
    "arity": -2,
    "flags": [
      "readonly"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "mget": {
    "arity": -2,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "migrate": {
    "arity": -6,
    "flags": [
      "write",
      "movablekeys"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "module": {
    "arity": -2,
    "flags": [
      "admin",
      "noscript"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "monitor": {
    "arity": 1,
    "flags": [
      "admin",
      "noscript"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "move": {
    "arity": 3,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "mset": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 2
  },
  "msetnx": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 2
  },
  "multi": {
    "arity": 1,
    "flags": [
      "noscript",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "object": {
    "arity": 3,
    "flags": [
      "readonly"
    ],
    "keyStart": 2,
    "keyStop": 2,
    "step": 2
  },
  "persist": {
    "arity": 2,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "pexpire": {
    "arity": 3,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "pexpireat": {
    "arity": 3,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "pfadd": {
    "arity": -2,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "pfcount": {
    "arity": -2,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "pfdebug": {
    "arity": -3,
    "flags": [
      "write"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "pfmerge": {
    "arity": -2,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "pfselftest": {
    "arity": 1,
    "flags": [
      "admin"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "ping": {
    "arity": -1,
    "flags": [
      "stale",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "post": {
    "arity": -1,
    "flags": [
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "psetex": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "psubscribe": {
    "arity": -2,
    "flags": [
      "pubsub",
      "noscript",
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "psync": {
    "arity": 3,
    "flags": [
      "readonly",
      "admin",
      "noscript"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "pttl": {
    "arity": 2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "publish": {
    "arity": 3,
    "flags": [
      "pubsub",
      "loading",
      "stale",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "pubsub": {
    "arity": -2,
    "flags": [
      "pubsub",
      "random",
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "punsubscribe": {
    "arity": -1,
    "flags": [
      "pubsub",
      "noscript",
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "quit": {
    "arity": 1,
    "flags": [
      "loading",
      "stale",
      "readonly"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "randomkey": {
    "arity": 1,
    "flags": [
      "readonly",
      "random"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "readonly": {
    "arity": 1,
    "flags": [
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "readwrite": {
    "arity": 1,
    "flags": [
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "rename": {
    "arity": 3,
    "flags": [
      "write"
    ],
    "keyStart": 1,
    "keyStop": 2,
    "step": 1
  },
  "renamenx": {
    "arity": 3,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 2,
    "step": 1
  },
  "replconf": {
    "arity": -1,
    "flags": [
      "admin",
      "noscript",
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "restore": {
    "arity": -4,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "restore-asking": {
    "arity": -4,
    "flags": [
      "write",
      "denyoom",
      "asking"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "role": {
    "arity": 1,
    "flags": [
      "noscript",
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "rpop": {
    "arity": 2,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "rpoplpush": {
    "arity": 3,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 2,
    "step": 1
  },
  "rpush": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "rpushx": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "sadd": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "save": {
    "arity": 1,
    "flags": [
      "admin",
      "noscript"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "scan": {
    "arity": -2,
    "flags": [
      "readonly",
      "random"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "scard": {
    "arity": 2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "script": {
    "arity": -2,
    "flags": [
      "noscript"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "sdiff": {
    "arity": -2,
    "flags": [
      "readonly",
      "sort_for_script"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "sdiffstore": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "select": {
    "arity": 2,
    "flags": [
      "loading",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "set": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "setbit": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "setex": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "setnx": {
    "arity": 3,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "setrange": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "shutdown": {
    "arity": -1,
    "flags": [
      "admin",
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "sinter": {
    "arity": -2,
    "flags": [
      "readonly",
      "sort_for_script"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "sinterstore": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "sismember": {
    "arity": 3,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "slaveof": {
    "arity": 3,
    "flags": [
      "admin",
      "noscript",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "slowlog": {
    "arity": -2,
    "flags": [
      "admin"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "smembers": {
    "arity": 2,
    "flags": [
      "readonly",
      "sort_for_script"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "smove": {
    "arity": 4,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 2,
    "step": 1
  },
  "sort": {
    "arity": -2,
    "flags": [
      "write",
      "denyoom",
      "movablekeys"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "spop": {
    "arity": -2,
    "flags": [
      "write",
      "random",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "srandmember": {
    "arity": -2,
    "flags": [
      "readonly",
      "random"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "srem": {
    "arity": -3,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "sscan": {
    "arity": -3,
    "flags": [
      "readonly",
      "random"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "strlen": {
    "arity": 2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "subscribe": {
    "arity": -2,
    "flags": [
      "pubsub",
      "noscript",
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "substr": {
    "arity": 4,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "sunion": {
    "arity": -2,
    "flags": [
      "readonly",
      "sort_for_script"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "sunionstore": {
    "arity": -3,
    "flags": [
      "write",
      "denyoom"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "swapdb": {
    "arity": 3,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "sync": {
    "arity": 1,
    "flags": [
      "readonly",
      "admin",
      "noscript"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "time": {
    "arity": 1,
    "flags": [
      "random",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "touch": {
    "arity": -2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "ttl": {
    "arity": 2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "type": {
    "arity": 2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "unlink": {
    "arity": -2,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "unsubscribe": {
    "arity": -1,
    "flags": [
      "pubsub",
      "noscript",
      "loading",
      "stale"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "unwatch": {
    "arity": 1,
    "flags": [
      "noscript",
      "fast"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "wait": {
    "arity": 3,
    "flags": [
      "noscript"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "watch": {
    "arity": -2,
    "flags": [
      "noscript",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": -1,
    "step": 1
  },
  "zadd": {
    "arity": -4,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zcard": {
    "arity": 2,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zcount": {
    "arity": 4,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zincrby": {
    "arity": 4,
    "flags": [
      "write",
      "denyoom",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zinterstore": {
    "arity": -4,
    "flags": [
      "write",
      "denyoom",
      "movablekeys"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  },
  "zlexcount": {
    "arity": 4,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zrange": {
    "arity": -4,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zrangebylex": {
    "arity": -4,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zrangebyscore": {
    "arity": -4,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zrank": {
    "arity": 3,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zrem": {
    "arity": -3,
    "flags": [
      "write",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zremrangebylex": {
    "arity": 4,
    "flags": [
      "write"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zremrangebyrank": {
    "arity": 4,
    "flags": [
      "write"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zremrangebyscore": {
    "arity": 4,
    "flags": [
      "write"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zrevrange": {
    "arity": -4,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zrevrangebylex": {
    "arity": -4,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zrevrangebyscore": {
    "arity": -4,
    "flags": [
      "readonly"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zrevrank": {
    "arity": 3,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zscan": {
    "arity": -3,
    "flags": [
      "readonly",
      "random"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zscore": {
    "arity": 3,
    "flags": [
      "readonly",
      "fast"
    ],
    "keyStart": 1,
    "keyStop": 1,
    "step": 1
  },
  "zunionstore": {
    "arity": -4,
    "flags": [
      "write",
      "denyoom",
      "movablekeys"
    ],
    "keyStart": 0,
    "keyStop": 0,
    "step": 0
  }
}

+ 155 - 0
src/doctor/node_modules/redis/node_modules/redis-commands/index.js

@ -0,0 +1,155 @@
'use strict'
var commands = require('./commands')
/**
 * Redis command list
 *
 * All commands are lowercased.
 *
 * @var {string[]}
 * @public
 */
exports.list = Object.keys(commands)
var flags = {}
exports.list.forEach(function (commandName) {
  flags[commandName] = commands[commandName].flags.reduce(function (flags, flag) {
    flags[flag] = true
    return flags
  }, {})
})
/**
 * Check if the command exists
 *
 * @param {string} commandName - the command name
 * @return {boolean} result
 * @public
 */
exports.exists = function (commandName) {
  return Boolean(commands[commandName])
}
/**
 * Check if the command has the flag
 *
 * Some of possible flags: readonly, noscript, loading
 * @param {string} commandName - the command name
 * @param {string} flag - the flag to check
 * @return {boolean} result
 * @public
 */
exports.hasFlag = function (commandName, flag) {
  if (!flags[commandName]) {
    throw new Error('Unknown command ' + commandName)
  }
  return Boolean(flags[commandName][flag])
}
/**
 * Get indexes of keys in the command arguments
 *
 * @param {string} commandName - the command name
 * @param {string[]} args - the arguments of the command
 * @param {object} [options] - options
 * @param {boolean} [options.parseExternalKey] - parse external keys
 * @return {number[]} - the list of the index
 * @public
 *
 * @example
 * ```javascript
 * getKeyIndexes('set', ['key', 'value']) // [0]
 * getKeyIndexes('mget', ['key1', 'key2']) // [0, 1]
 * ```
 */
exports.getKeyIndexes = function (commandName, args, options) {
  var command = commands[commandName]
  if (!command) {
    throw new Error('Unknown command ' + commandName)
  }
  if (!Array.isArray(args)) {
    throw new Error('Expect args to be an array')
  }
  var keys = []
  var i, keyStart, keyStop, parseExternalKey
  switch (commandName) {
    case 'zunionstore':
    case 'zinterstore':
      keys.push(0)
    // fall through
    case 'eval':
    case 'evalsha':
      keyStop = Number(args[1]) + 2
      for (i = 2; i < keyStop; i++) {
        keys.push(i)
      }
      break
    case 'sort':
      parseExternalKey = options && options.parseExternalKey
      keys.push(0)
      for (i = 1; i < args.length - 1; i++) {
        if (typeof args[i] !== 'string') {
          continue
        }
        var directive = args[i].toUpperCase()
        if (directive === 'GET') {
          i += 1
          if (args[i] !== '#') {
            if (parseExternalKey) {
              keys.push([i, getExternalKeyNameLength(args[i])])
            } else {
              keys.push(i)
            }
          }
        } else if (directive === 'BY') {
          i += 1
          if (parseExternalKey) {
            keys.push([i, getExternalKeyNameLength(args[i])])
          } else {
            keys.push(i)
          }
        } else if (directive === 'STORE') {
          i += 1
          keys.push(i)
        }
      }
      break
    case 'migrate':
      if (args[2] === '') {
        for (i = 5; i < args.length - 1; i++) {
          if (args[i].toUpperCase() === 'KEYS') {
            for (var j = i + 1; j < args.length; j++) {
              keys.push(j)
            }
            break
          }
        }
      } else {
        keys.push(2)
      }
      break
    default:
    // step has to be at least one in this case, otherwise the command does not contain a key
      if (command.step > 0) {
        keyStart = command.keyStart - 1
        keyStop = command.keyStop > 0 ? command.keyStop : args.length + command.keyStop + 1
        for (i = keyStart; i < keyStop; i += command.step) {
          keys.push(i)
        }
      }
      break
  }
  return keys
}
function getExternalKeyNameLength (key) {
  if (typeof key !== 'string') {
    key = String(key)
  }
  var hashPos = key.indexOf('->')
  return hashPos === -1 ? key.length : hashPos
}

+ 78 - 0
src/doctor/node_modules/redis/node_modules/redis-commands/package.json

@ -0,0 +1,78 @@
{
  "name": "redis-commands",
  "version": "1.3.0",
  "description": "Redis commands",
  "main": "index.js",
  "scripts": {
    "pretest": "npm run lint",
    "test": "mocha",
    "posttest": "npm run coverage && npm run coverage:check",
    "coverage": "node ./node_modules/istanbul/lib/cli.js cover --preserve-comments ./node_modules/mocha/bin/_mocha -- -R spec",
    "coverage:check": "node ./node_modules/istanbul/lib/cli.js check-coverage --branch 100 --statement 100",
    "build": "node tools/build",
    "lint": "standard --fix --verbose | snazzy"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/NodeRedis/redis-commands.git"
  },
  "keywords": [
    "redis",
    "commands",
    "prefix"
  ],
  "author": {
    "name": "luin",
    "email": "i@zihua.li",
    "url": "http://zihua.li"
  },
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/NodeRedis/redis-commonds/issues"
  },
  "homepage": "https://github.com/NodeRedis/redis-commonds",
  "devDependencies": {
    "chai": "^3.4.0",
    "codeclimate-test-reporter": "^0.3.1",
    "ioredis": "^2.0.0",
    "istanbul": "^0.4.3",
    "json-stable-stringify": "^1.0.0",
    "mocha": "^3.0.0",
    "snazzy": "^5.0.0",
    "standard": "^8.0.0"
  },
  "gitHead": "a3ea72da0064b90f83b5d4728b60d0bb3021ed6a",
  "_id": "redis-commands@1.3.0",
  "_shasum": "4307d8094aee1315829ab6729b37b99f62365d63",
  "_from": "redis-commands@>=1.2.0 <2.0.0",
  "_npmVersion": "3.10.8",
  "_nodeVersion": "6.8.0",
  "_npmUser": {
    "name": "bridgear",
    "email": "ruben@bridgewater.de"
  },
  "dist": {
    "shasum": "4307d8094aee1315829ab6729b37b99f62365d63",
    "size": 7294,
    "noattachment": false,
    "tarball": "http://registry.npm.taobao.org/redis-commands/download/redis-commands-1.3.0.tgz"
  },
  "maintainers": [
    {
      "name": "bridgear",
      "email": "ruben@bridgewater.de"
    },
    {
      "name": "luin",
      "email": "i@zihua.li"
    }
  ],
  "_npmOperationalInternal": {
    "host": "packages-12-west.internal.npmjs.com",
    "tmp": "tmp/redis-commands-1.3.0.tgz_1476993178910_0.8879043820779771"
  },
  "directories": {},
  "publish_time": 1476993180963,
  "_cnpm_publish_time": 1476993180963,
  "_resolved": "https://registry.npm.taobao.org/redis-commands/download/redis-commands-1.3.0.tgz"
}

+ 120 - 0
src/doctor/node_modules/redis/node_modules/redis-commands/test/index.js

@ -0,0 +1,120 @@
'use strict'
/* global describe, it */
var commands = require('..')
var expect = require('chai').expect
describe('redis-commands', function () {
  describe('.list', function () {
    it('should be an array', function () {
      expect(commands.list).to.be.instanceof(Array)
    })
    it('should ensure every command is lowercase', function () {
      commands.list.forEach(function (command) {
        expect(command.toLowerCase()).to.eql(command)
      })
    })
    it('should ensure quit command is added to the commands list', function () {
      expect(commands.list.indexOf('quit')).not.to.eql(-1)
    })
    it('should not contain multi-word commands', function () {
      commands.list.forEach(function (command) {
        expect(command.indexOf(' ')).to.eql(-1)
      })
    })
  })
  describe('.exists()', function () {
    it('should return true for existing commands', function () {
      expect(commands.exists('set')).to.eql(true)
      expect(commands.exists('get')).to.eql(true)
      expect(commands.exists('cluster')).to.eql(true)
      expect(commands.exists('quit')).to.eql(true)
      expect(commands.exists('config')).to.eql(true)
    })
    it('should return false for non-existing commands', function () {
      expect(commands.exists('SET')).to.eql(false)
      expect(commands.exists('set get')).to.eql(false)
      expect(commands.exists('other-command')).to.eql(false)
    })
  })
  describe('.hasFlag()', function () {
    it('should return true if the command has the flag', function () {
      expect(commands.hasFlag('set', 'write')).to.eql(true)
      expect(commands.hasFlag('set', 'denyoom')).to.eql(true)
      expect(commands.hasFlag('select', 'fast')).to.eql(true)
    })
    it('should return false otherwise', function () {
      expect(commands.hasFlag('set', 'fast')).to.eql(false)
      expect(commands.hasFlag('set', 'readonly')).to.eql(false)
      expect(commands.hasFlag('select', 'denyoom')).to.eql(false)
      expect(commands.hasFlag('quit', 'denyoom')).to.eql(false)
    })
    it('should throw on unknown commands', function () {
      expect(function () { commands.hasFlag('UNKNOWN') }).to.throw(Error)
    })
  })
  describe('.getKeyIndexes()', function () {
    var index = commands.getKeyIndexes
    it('should throw on unknown commands', function () {
      expect(function () { index('UNKNOWN') }).to.throw(Error)
    })
    it('should throw on faulty args', function () {
      expect(function () { index('get', 'foo') }).to.throw(Error)
    })
    it('should return an empty array if no keys exist', function () {
      expect(index('auth', [])).to.eql([])
    })
    it('should return key indexes', function () {
      expect(index('set', ['foo', 'bar'])).to.eql([0])
      expect(index('del', ['foo'])).to.eql([0])
      expect(index('get', ['foo'])).to.eql([0])
      expect(index('mget', ['foo', 'bar'])).to.eql([0, 1])
      expect(index('mset', ['foo', 'v1', 'bar', 'v2'])).to.eql([0, 2])
      expect(index('hmset', ['key', 'foo', 'v1', 'bar', 'v2'])).to.eql([0])
      expect(index('blpop', ['key1', 'key2', '17'])).to.eql([0, 1])
      expect(index('evalsha', ['23123', '2', 'foo', 'bar', 'zoo'])).to.eql([2, 3])
      expect(index('sort', ['key'])).to.eql([0])
      expect(index('zunionstore', ['out', '2', 'zset1', 'zset2', 'WEIGHTS', '2', '3'])).to.eql([0, 2, 3])
      expect(index('migrate', ['127.0.0.1', 6379, 'foo', 0, 0, 'COPY'])).to.eql([2])
      expect(index('migrate', ['127.0.0.1', 6379, '', 0, 0, 'REPLACE', 'KEYS', 'foo', 'bar'])).to.eql([7, 8])
      expect(index('migrate', ['127.0.0.1', 6379, '', 0, 0, 'KEYS', 'foo', 'bar'])).to.eql([6, 7])
    })
    it('should support numeric argument', function () {
      expect(index('evalsha', ['23123', 2, 'foo', 'bar', 'zoo'])).to.eql([2, 3])
      expect(index('zinterstore', ['out', 2, 'zset1', 'zset2', 'WEIGHTS', 2, 3])).to.eql([0, 2, 3])
    })
    describe('disable parseExternalKey', function () {
      it('should not parse external keys', function () {
        expect(index('sort', ['key', 'BY', 'hash:*->field'])).to.eql([0, 2])
        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])
      })
    })
    describe('enable parseExternalKey', function () {
      it('should parse external keys', function () {
        expect(index('sort', ['key', 'BY', 'hash:*->field'], {
          parseExternalKey: true
        })).to.eql([0, [2, 6]])
        expect(index('sort', ['key', 'BY', 'hash:*->field', 'LIMIT', 2, 3, 'GET', new Buffer('gk'), 'GET', '#', 'Get', 'gh->f*', 'DESC', 'ALPHA', 'STORE', 'store'], {
          parseExternalKey: true
        })).to.eql([0, [2, 6], [7, 2], [11, 2], 15])
      })
    })
  })
})

+ 62 - 0
src/doctor/node_modules/redis/node_modules/redis-commands/tools/build.js

@ -0,0 +1,62 @@
var fs = require('fs')
var path = require('path')
var stringify = require('json-stable-stringify')
var commandPath = path.join(__dirname, '..', 'commands.json')
var redisCommands = require('../')
var Redis = require('ioredis')
var redis = new Redis(process.env.REDIS_URI)
redis.command().then(function (res) {
  redis.disconnect()
  // Find all special handled cases
  var movableKeys = String(redisCommands.getKeyIndexes).match(/case '[a-z-]+':/g).map(function (entry) {
    return entry.replace(/^case '|':$/g, '')
  })
  var commands = res.reduce(function (prev, current) {
    var currentCommandPos = movableKeys.indexOf(current[0])
    if (currentCommandPos !== -1 && current[2].indexOf('movablekeys') !== -1) {
      movableKeys.splice(currentCommandPos, 1)
    }
    // https://github.com/antirez/redis/issues/2598
    if (current[0] === 'brpop' && current[4] === 1) {
      current[4] = -2
    }
    prev[current[0]] = {
      arity: current[1] || 1, // https://github.com/antirez/redis/pull/2986
      flags: current[2],
      keyStart: current[3],
      keyStop: current[4],
      step: current[5]
    }
    return prev
  }, {})
  // Future proof. Redis might implement this at some point
  // https://github.com/antirez/redis/pull/2982
  if (!commands.quit) {
    commands.quit = {
      arity: 1,
      flags: [
        'loading',
        'stale',
        'readonly'
      ],
      keyStart: 0,
      keyStop: 0,
      step: 0
    }
  }
  if (movableKeys.length !== 0) {
    throw new Error('Not all commands (\'' + movableKeys.join('\', \'') + '\') with the "movablekeys" flag are handled in the code')
  }
  // Use json-stable-stringify instead fo JSON.stringify
  // for easier diffing
  var content = stringify(commands, { space: '  ' })
  fs.writeFile(commandPath, content)
})

+ 6 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/.codeclimate.yml

@ -0,0 +1,6 @@
languages:
  JavaScript: true
exclude_paths:
- "benchmark/index.js"
- "benchmark/old/javascript.js"
- "test/parsers.spec.js"

+ 12 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/.npmignore

@ -0,0 +1,12 @@
# IntelliJ project files
.idea
*.iml
out
gen
# Unrelevant files and folders
benchmark
coverage
test
.travis.yml
.gitignore

+ 22 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/LICENSE

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 NodeRedis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large
+ 149 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/README.md


+ 101 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/changelog.md

@ -0,0 +1,101 @@
## v.2.3.0 - 25 Nov, 2016
Features
-  Parsing time for big arrays (e.g. 4mb+) is now linear and works well for arbitrary array sizes
This case is a magnitude faster than before
    OLD STR: * big array x 1.09 ops/sec ±2.15% (7 runs sampled)
    OLD BUF: * big array x 1.23 ops/sec ±2.67% (8 runs sampled)
    NEW STR: * big array x 273 ops/sec ±2.09% (85 runs sampled)
    NEW BUF: * big array x 259 ops/sec ±1.32% (85 runs sampled)
    (~10mb array with 1000 entries)
## v.2.2.0 - 18 Nov, 2016
Features
-  Improve `stringNumbers` parsing performance by up to 100%
Bugfixes
-  Do not unref the interval anymore due to issues with NodeJS
## v.2.1.1 - 31 Oct, 2016
Bugfixes
-  Remove erroneously added const to support Node.js 0.10
## v.2.1.0 - 30 Oct, 2016
Features
-  Improve parser errors by adding more detailed information to them
-  Accept manipulated Object.prototypes
-  Unref the interval if used
## v.2.0.4 - 21 Jul, 2016
Bugfixes
-  Fixed multi byte characters getting corrupted
## v.2.0.3 - 17 Jun, 2016
Bugfixes
-  Fixed parser not working with huge buffers (e.g. 300 MB)
## v.2.0.2 - 08 Jun, 2016
Bugfixes
-  Fixed parser with returnBuffers option returning corrupted data
## v.2.0.1 - 04 Jun, 2016
Bugfixes
-  Fixed multiple parsers working concurrently resulting in faulty data in some cases
## v.2.0.0 - 29 May, 2016
The javascript parser got completly 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.
Therefore the hiredis parser was deprecated and should only be used for testing purposes and benchmarking comparison.
All Errors returned by the parser are from now on of class ReplyError
Features
-  Improved performance by up to 15x as fast as before
-  Improved options validation
-  Added ReplyError Class
-  Added parser benchmark
-  Switched default parser from hiredis to JS, no matter if hiredis is installed or not
Removed
-  Deprecated hiredis support
## v.1.3.0 - 27 Mar, 2016
Features
-  Added `auto` as parser name option to check what parser is available
-  Non existing requested parsers falls back into auto mode instead of always choosing the JS parser
## v.1.2.0 - 27 Mar, 2016
Features
-  Added `stringNumbers` option to make sure all numbers are returned as string instead of a js number for precision
-  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
## v.1.1.0 - 26 Jan, 2016
Features
-  The parser is from now on going to reset itself on protocol errors

+ 4 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/index.js

@ -0,0 +1,4 @@
'use strict'
module.exports = require('./lib/parser')
module.exports.ReplyError = require('./lib/replyError')

+ 1 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/isolate-0x28e7b50-v8.log

@ -0,0 +1 @@
v8-version,5,4,500,36,0

+ 1 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/isolate-0x3615b50-v8.log

@ -0,0 +1 @@
v8-version,5,4,500,36,0

+ 52 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/lib/hiredis.js

@ -0,0 +1,52 @@
'use strict'
var hiredis = require('hiredis')
var ReplyError = require('../lib/replyError')
/**
 * Parse data
 * @param parser
 * @returns {*}
 */
function parseData (parser) {
  try {
    return parser.reader.get()
  } catch (err) {
    // Protocol errors land here
    // Reset the parser. Otherwise new commands can't be processed properly
    parser.reader = new hiredis.Reader(parser.options)
    parser.returnFatalError(new ReplyError(err.message))
  }
}
/**
 * Hiredis Parser
 * @param options
 * @constructor
 */
function HiredisReplyParser (options) {
  this.returnError = options.returnError
  this.returnFatalError = options.returnFatalError || options.returnError
  this.returnReply = options.returnReply
  this.name = 'hiredis'
  this.options = {
    return_buffers: !!options.returnBuffers
  }
  this.reader = new hiredis.Reader(this.options)
}
HiredisReplyParser.prototype.execute = function (data) {
  this.reader.feed(data)
  var reply = parseData(this)
  while (reply !== undefined) {
    if (reply && reply.name === 'Error') {
      this.returnError(new ReplyError(reply.message))
    } else {
      this.returnReply(reply)
    }
    reply = parseData(this)
  }
}
module.exports = HiredisReplyParser

+ 498 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/lib/parser.js

@ -0,0 +1,498 @@
'use strict'
var StringDecoder = require('string_decoder').StringDecoder
var decoder = new StringDecoder()
var ReplyError = require('./replyError')
var bufferPool = new Buffer(32 * 1024)
var bufferOffset = 0
var interval = null
var counter = 0
var notDecreased = 0
/**
 * Used for lengths and numbers only, faster perf on arrays / bulks
 * @param parser
 * @returns {*}
 */
function parseSimpleNumbers (parser) {
  var offset = parser.offset
  var length = parser.buffer.length - 1
  var number = 0
  var sign = 1
  if (parser.buffer[offset] === 45) {
    sign = -1
    offset++
  }
  while (offset < length) {
    var c1 = parser.buffer[offset++]
    if (c1 === 13) { // \r\n
      parser.offset = offset + 1
      return sign * number
    }
    number = (number * 10) + (c1 - 48)
  }
}
/**
 * Used for integer numbers in case of the returnNumbers option
 *
 * The maximimum possible integer to use is: Math.floor(Number.MAX_SAFE_INTEGER / 10)
 * Staying in a SMI Math.floor((Math.pow(2, 32) / 10) - 1) is even more efficient though
 *
 * @param parser
 * @returns {*}
 */
function parseStringNumbers (parser) {
  var offset = parser.offset
  var length = parser.buffer.length - 1
  var number = 0
  var res = ''
  if (parser.buffer[offset] === 45) {
    res += '-'
    offset++
  }
  while (offset < length) {
    var c1 = parser.buffer[offset++]
    if (c1 === 13) { // \r\n
      parser.offset = offset + 1
      if (number !== 0) {
        res += number
      }
      return res
    } else if (number > 429496728) {
      res += (number * 10) + (c1 - 48)
      number = 0
    } else if (c1 === 48 && number === 0) {
      res += 0
    } else {
      number = (number * 10) + (c1 - 48)
    }
  }
}
/**
 * Returns a string or buffer of the provided offset start and
 * end ranges. Checks `optionReturnBuffers`.
 *
 * If returnBuffers is active, all return values are returned as buffers besides numbers and errors
 *
 * @param parser
 * @param start
 * @param end
 * @returns {*}
 */
function convertBufferRange (parser, start, end) {
  parser.offset = end + 2
  if (parser.optionReturnBuffers === true) {
    return parser.buffer.slice(start, end)
  }
  return parser.buffer.toString('utf-8', start, end)
}
/**
 * Parse a '+' redis simple string response but forward the offsets
 * onto convertBufferRange to generate a string.
 * @param parser
 * @returns {*}
 */
function parseSimpleStringViaOffset (parser) {
  var start = parser.offset
  var offset = start
  var buffer = parser.buffer
  var length = buffer.length - 1
  while (offset < length) {
    if (buffer[offset++] === 13) { // \r\n
      return convertBufferRange(parser, start, offset - 1)
    }
  }
}
/**
 * Returns the string length via parseSimpleNumbers
 * @param parser
 * @returns {*}
 */
function parseLength (parser) {
  var string = parseSimpleNumbers(parser)
  if (string !== undefined) {
    return string
  }
}
/**
 * Parse a ':' redis integer response
 *
 * If stringNumbers is activated the parser always returns numbers as string
 * This is important for big numbers (number > Math.pow(2, 53)) as js numbers
 * are 64bit floating point numbers with reduced precision
 *
 * @param parser
 * @returns {*}
 */
function parseInteger (parser) {
  if (parser.optionStringNumbers) {
    return parseStringNumbers(parser)
  }
  return parseSimpleNumbers(parser)
}
/**
 * Parse a '$' redis bulk string response
 * @param parser
 * @returns {*}
 */
function parseBulkString (parser) {
  var length = parseLength(parser)
  if (length === undefined) {
    return
  }
  if (length === -1) {
    return null
  }
  var offsetEnd = parser.offset + length
  if (offsetEnd + 2 > parser.buffer.length) {
    parser.bigStrSize = offsetEnd + 2
    parser.bigOffset = parser.offset
    parser.totalChunkSize = parser.buffer.length
    parser.bufferCache.push(parser.buffer)
    return
  }
  return convertBufferRange(parser, parser.offset, offsetEnd)
}
/**
 * Parse a '-' redis error response
 * @param parser
 * @returns {Error}
 */
function parseError (parser) {
  var string = parseSimpleStringViaOffset(parser)
  if (string !== undefined) {
    if (parser.optionReturnBuffers === true) {
      string = string.toString()
    }
    return new ReplyError(string)
  }
}
/**
 * Parsing error handler, resets parser buffer
 * @param parser
 * @param error
 */
function handleError (parser, error) {
  parser.buffer = null
  parser.returnFatalError(error)
}
/**
 * Parse a '*' redis array response
 * @param parser
 * @returns {*}
 */
function parseArray (parser) {
  var length = parseLength(parser)
  if (length === undefined) {
    return
  }
  if (length === -1) {
    return null
  }
  var responses = new Array(length)
  return parseArrayElements(parser, responses, 0)
}
/**
 * Parse chunked redis array response
 * @param parser
 * @returns {*}
 */
function parseArrayChunks (parser) {
  return parseArrayElements(parser, parser.arrayCache, parser.arrayPos)
}
/**
 * Parse redis array response elements
 * @param parser
 * @param responses
 * @param i
 * @returns {*}
 */
function parseArrayElements (parser, responses, i) {
  var bufferLength = parser.buffer.length
  while (i < responses.length) {
    var offset = parser.offset
    if (parser.offset >= bufferLength) {
      parser.arrayCache = responses
      parser.arrayPos = i
      parser.offset = offset
      return
    }
    var response = parseType(parser, parser.buffer[parser.offset++])
    if (response === undefined) {
      parser.arrayCache = responses
      parser.arrayPos = i
      parser.offset = offset
      return
    }
    responses[i] = response
    i++
  }
  return responses
}
/**
 * Called the appropriate parser for the specified type.
 * @param parser
 * @param type
 * @returns {*}
 */
function parseType (parser, type) {
  switch (type) {
    case 36: // $
      return parseBulkString(parser)
    case 58: // :
      return parseInteger(parser)
    case 43: // +
      return parseSimpleStringViaOffset(parser)
    case 42: // *
      return parseArray(parser)
    case 45: // -
      return parseError(parser)
    default:
      var err = new ReplyError('Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte', 20)
      err.offset = parser.offset
      err.buffer = JSON.stringify(parser.buffer)
      return handleError(parser, err)
  }
}
// All allowed options including their typeof value
var optionTypes = {
  returnError: 'function',
  returnFatalError: 'function',
  returnReply: 'function',
  returnBuffers: 'boolean',
  stringNumbers: 'boolean',
  name: 'string'
}
/**
 * Javascript Redis Parser
 * @param options
 * @constructor
 */
function JavascriptRedisParser (options) {
  if (!(this instanceof JavascriptRedisParser)) {
    return new JavascriptRedisParser(options)
  }
  if (!options || !options.returnError || !options.returnReply) {
    throw new TypeError('Please provide all return functions while initiating the parser')
  }
  for (var key in options) {
    if (optionTypes.hasOwnProperty(key) && typeof options[key] !== optionTypes[key]) {
      throw new TypeError('The options argument contains the property "' + key + '" that is either unkown or of a wrong type')
    }
  }
  if (options.name === 'hiredis') {
    /* istanbul ignore next: hiredis is only supported for legacy usage */
    try {
      var Hiredis = require('./hiredis')
      console.error(new TypeError('Using hiredis is discouraged. Please use the faster JS parser by removing the name option.').stack.replace('Error', 'Warning'))
      return new Hiredis(options)
    } catch (e) {
      console.error(new TypeError('Hiredis is not installed. Please remove the `name` option. The (faster) JS parser is used instead.').stack.replace('Error', 'Warning'))
    }
  }
  this.optionReturnBuffers = !!options.returnBuffers
  this.optionStringNumbers = !!options.stringNumbers
  this.returnError = options.returnError
  this.returnFatalError = options.returnFatalError || options.returnError
  this.returnReply = options.returnReply
  this.name = 'javascript'
  this.offset = 0
  this.buffer = null
  this.bigStrSize = 0
  this.bigOffset = 0
  this.totalChunkSize = 0
  this.bufferCache = []
  this.arrayCache = null
  this.arrayPos = 0
}
/**
 * Concat a bulk string containing multiple chunks
 *
 * Notes:
 * 1) The first chunk might contain the whole bulk string including the \r
 * 2) We are only safe to fully add up elements that are neither the first nor any of the last two elements
 *
 * @param parser
 * @param buffer
 * @returns {String}
 */
function concatBulkString (parser) {
  var list = parser.bufferCache
  var chunks = list.length
  var offset = parser.bigStrSize - parser.totalChunkSize
  parser.offset = offset
  if (offset === 1) {
    if (chunks === 2) {
      return list[0].toString('utf8', parser.bigOffset, list[0].length - 1)
    }
    chunks--
  }
  var res = decoder.write(list[0].slice(parser.bigOffset))
  for (var i = 1; i < chunks - 1; i++) {
    res += decoder.write(list[i])
  }
  res += decoder.end(list[i].slice(0, offset - 2))
  return res
}
/**
 * Decrease the bufferPool size over time
 * @returns {undefined}
 */
function decreaseBufferPool () {
  if (bufferPool.length > 50 * 1024) {
    // Balance between increasing and decreasing the bufferPool
    if (counter === 1 || notDecreased > counter * 2) {
      // Decrease the bufferPool by 10% by removing the first 10% of the current pool
      var sliceLength = Math.floor(bufferPool.length / 10)
      if (bufferOffset <= sliceLength) {
        bufferOffset = 0
      } else {
        bufferOffset -= sliceLength
      }
      bufferPool = bufferPool.slice(sliceLength, bufferPool.length)
    } else {
      notDecreased++
      counter--
    }
  } else {
    clearInterval(interval)
    counter = 0
    notDecreased = 0
    interval = null
  }
}
/**
 * Concat the collected chunks from parser.bufferCache
 * @param parser
 * @param length
 * @returns {Buffer}
 */
function concatBuffer (parser, length) {
  var list = parser.bufferCache
  var pos = bufferOffset
  length -= parser.offset
  if (bufferPool.length < length + bufferOffset) {
    // Increase the bufferPool size
    var multiplier = length > 1024 * 1024 * 75 ? 2 : 3
    if (bufferOffset > 1024 * 1024 * 120) {
      bufferOffset = 1024 * 1024 * 50
    }
    bufferPool = new Buffer(length * multiplier + bufferOffset)
    bufferOffset = 0
    counter++
    pos = 0
    if (interval === null) {
      interval = setInterval(decreaseBufferPool, 50)
    }
  }
  list[0].copy(bufferPool, pos, parser.offset, list[0].length)
  pos += list[0].length - parser.offset
  for (var i = 1; i < list.length; i++) {
    list[i].copy(bufferPool, pos)
    pos += list[i].length
  }
  var buffer = bufferPool.slice(bufferOffset, length + bufferOffset)
  bufferOffset += length
  return buffer
}
/**
 * Parse the redis buffer
 * @param buffer
 * @returns {undefined}
 */
JavascriptRedisParser.prototype.execute = function execute (buffer) {
  var arr
  if (this.buffer === null) {
    this.buffer = buffer
    this.offset = 0
  } else if (this.bigStrSize === 0) {
    var oldLength = this.buffer.length
    var remainingLength = oldLength - this.offset
    var newBuffer = new Buffer(remainingLength + buffer.length)
    this.buffer.copy(newBuffer, 0, this.offset, oldLength)
    buffer.copy(newBuffer, remainingLength, 0, buffer.length)
    this.buffer = newBuffer
    this.offset = 0
    if (this.arrayCache) {
      arr = parseArrayChunks(this)
      if (!arr) {
        return
      }
      this.returnReply(arr)
      this.arrayCache = null
    }
  } else if (this.totalChunkSize + buffer.length >= this.bigStrSize) {
    this.bufferCache.push(buffer)
    if (this.optionReturnBuffers === false && !this.arrayCache) {
      this.returnReply(concatBulkString(this))
      this.buffer = buffer
    } else {
      this.buffer = concatBuffer(this, this.totalChunkSize + buffer.length)
      this.offset = 0
      if (this.arrayCache) {
        arr = parseArrayChunks(this)
        if (!arr) {
          this.bigStrSize = 0
          this.bufferCache = []
          return
        }
        this.returnReply(arr)
        this.arrayCache = null
      }
    }
    this.bigStrSize = 0
    this.bufferCache = []
  } else {
    this.bufferCache.push(buffer)
    this.totalChunkSize += buffer.length
    return
  }
  while (this.offset < this.buffer.length) {
    var offset = this.offset
    var type = this.buffer[this.offset++]
    var response = parseType(this, type)
    if (response === undefined) {
      if (!this.arrayCache) {
        this.offset = offset
      }
      return
    }
    if (type === 45) {
      this.returnError(response)
    } else {
      this.returnReply(response)
    }
  }
  this.buffer = null
}
module.exports = JavascriptRedisParser

+ 24 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/lib/replyError.js

@ -0,0 +1,24 @@
'use strict'
var util = require('util')
function ReplyError (message, newLimit) {
  var limit = Error.stackTraceLimit
  Error.stackTraceLimit = newLimit || 2
  Error.call(this, message)
  Error.captureStackTrace(this, this.constructor)
  Error.stackTraceLimit = limit
  Object.defineProperty(this, 'message', {
    value: message || '',
    writable: true
  })
}
util.inherits(ReplyError, Error)
Object.defineProperty(ReplyError.prototype, 'name', {
  value: 'ReplyError',
  writable: true
})
module.exports = ReplyError

+ 81 - 0
src/doctor/node_modules/redis/node_modules/redis-parser/package.json

@ -0,0 +1,81 @@
{
  "name": "redis-parser",
  "version": "2.3.0",
  "description": "Javascript Redis protocol (RESP) parser",
  "main": "index.js",
  "scripts": {
    "test": "npm run coverage",
    "benchmark": "node ./benchmark",
    "posttest": "standard && npm run coverage:check",
    "coverage": "node ./node_modules/istanbul/lib/cli.js cover --preserve-comments ./node_modules/mocha/bin/_mocha -- -R spec",
    "coverage:check": "node ./node_modules/istanbul/lib/cli.js check-coverage --branch 100 --statement 100"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/NodeRedis/node-redis-parser.git"
  },
  "keywords": [
    "redis",
    "protocol",
    "parser",
    "database",
    "javascript",
    "node",
    "nodejs",
    "resp",
    "hiredis"
  ],
  "engines": {
    "node": ">=0.10.0"
  },
  "devDependencies": {
    "benchmark": "^2.1.0",
    "codeclimate-test-reporter": "^0.4.0",
    "intercept-stdout": "^0.1.2",
    "istanbul": "^0.4.0",
    "standard": "^8.5.0",
    "mocha": "^3.1.2",
    "hiredis": "^0.5.0"
  },
  "author": {
    "name": "Ruben Bridgewater"
  },
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/NodeRedis/node-redis-parser/issues"
  },
  "homepage": "https://github.com/NodeRedis/node-redis-parser#readme",
  "directories": {
    "test": "test",
    "lib": "lib"
  },
  "gitHead": "26be8f841e43afd4d1453ae21b55df129f2aa271",
  "_id": "redis-parser@2.3.0",
  "_shasum": "313a47965e49ee35ab3a86c93388b403d76237f6",
  "_from": "redis-parser@>=2.0.0 <3.0.0",
  "_npmVersion": "3.10.9",
  "_nodeVersion": "7.1.0",
  "_npmUser": {
    "name": "bridgear",
    "email": "ruben@bridgewater.de"
  },
  "dist": {
    "shasum": "313a47965e49ee35ab3a86c93388b403d76237f6",
    "size": 8635,
    "noattachment": false,
    "tarball": "http://registry.npm.taobao.org/redis-parser/download/redis-parser-2.3.0.tgz"
  },
  "maintainers": [
    {
      "name": "bridgear",
      "email": "ruben@bridgewater.de"
    }
  ],
  "_npmOperationalInternal": {
    "host": "packages-18-east.internal.npmjs.com",
    "tmp": "tmp/redis-parser-2.3.0.tgz_1480100588628_0.9477839153259993"
  },
  "publish_time": 1480100589287,
  "_cnpm_publish_time": 1480100589287,
  "_resolved": "https://registry.npm.taobao.org/redis-parser/download/redis-parser-2.3.0.tgz"
}

+ 69 - 24
src/doctor/node_modules/redis/package.json

@ -1,58 +1,103 @@
{
  "name": "redis",
  "version": "0.12.1",
  "version": "2.6.3",
  "description": "Redis client library",
  "keywords": [
    "database",
    "redis",
    "database"
    "transaction",
    "pipelining",
    "performance",
    "queue",
    "nodejs",
    "pubsub",
    "backpressure"
  ],
  "author": {
    "name": "Matt Ranney",
    "email": "mjr@ranney.com"
  },
  "license": "MIT",
  "main": "./index.js",
  "scripts": {
    "test": "node ./test.js"
    "coveralls": "nyc report --reporter=text-lcov | coveralls",
    "coverage": "nyc report --reporter=html",
    "benchmark": "node benchmarks/multi_bench.js",
    "test": "nyc --cache mocha ./test/*.js ./test/commands/*.js --timeout=8000",
    "posttest": "eslint . --fix && npm run coverage",
    "compare": "node benchmarks/diff_multi_bench_output.js beforeBench.txt afterBench.txt"
  },
  "dependencies": {
    "double-ended-queue": "^2.1.0-0",
    "redis-commands": "^1.2.0",
    "redis-parser": "^2.0.0"
  },
  "engines": {
    "node": ">=0.10.0"
  },
  "devDependencies": {
    "metrics": ">=0.1.5",
    "colors": "~0.6.0-1",
    "underscore": "~1.4.4"
    "bluebird": "^3.0.2",
    "coveralls": "^2.11.2",
    "intercept-stdout": "~0.1.2",
    "eslint": "^2.5.0",
    "metrics": "^0.1.9",
    "mocha": "^2.3.2",
    "nyc": "^8.3.0",
    "tcp-port-used": "^0.1.2",
    "uuid": "^2.0.1",
    "win-spawn": "^2.0.0"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/mranney/node_redis.git"
    "url": "git://github.com/NodeRedis/node_redis.git"
  },
  "bugs": {
    "url": "https://github.com/mranney/node_redis/issues"
    "url": "https://github.com/NodeRedis/node_redis/issues"
  },
  "homepage": "https://github.com/NodeRedis/node_redis",
  "directories": {
    "example": "examples",
    "test": "test"
  },
  "homepage": "https://github.com/mranney/node_redis",
  "_id": "redis@0.12.1",
  "_shasum": "64df76ad0fc8acebaebd2a0645e8a48fac49185e",
  "_from": "redis@",
  "_npmVersion": "1.4.9",
  "gitHead": "be07c12fbd039b7b3b1cb5a71f06e24464bc0593",
  "_id": "redis@2.6.3",
  "_shasum": "84305b92553c6a1f09c7c47c30b11ace7dbb7ad4",
  "_from": "redis@latest",
  "_npmVersion": "3.10.8",
  "_nodeVersion": "7.0.0",
  "_npmUser": {
    "name": "bryce",
    "email": "bryce@ravenwall.com"
    "name": "bridgear",
    "email": "ruben@bridgewater.de"
  },
  "dist": {
    "shasum": "84305b92553c6a1f09c7c47c30b11ace7dbb7ad4",
    "size": 48569,
    "noattachment": false,
    "tarball": "http://registry.npm.taobao.org/redis/download/redis-2.6.3.tgz"
  },
  "maintainers": [
    {
      "name": "mjr",
      "email": "mjr@ranney.com"
      "name": "bcoe",
      "email": "bencoe@gmail.com"
    },
    {
      "name": "bridgear",
      "email": "ruben@bridgewater.de"
    },
    {
      "name": "bryce",
      "email": "bryce@ravenwall.com"
    },
    {
      "name": "dtrejo",
      "email": "david.daniel.trejo@gmail.com"
      "name": "mjr",
      "email": "mjr@ranney.com"
    }
  ],
  "dist": {
    "shasum": "64df76ad0fc8acebaebd2a0645e8a48fac49185e",
    "tarball": "http://registry.npmjs.org/redis/-/redis-0.12.1.tgz"
  "_npmOperationalInternal": {
    "host": "packages-18-east.internal.npmjs.com",
    "tmp": "tmp/redis-2.6.3.tgz_1477944575573_0.07418122352100909"
  },
  "directories": {},
  "_resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz"
  "publish_time": 1477944576286,
  "_cnpm_publish_time": 1477944576286,
  "_resolved": "https://registry.npm.taobao.org/redis/download/redis-2.6.3.tgz"
}

+ 1 - 0
src/doctor/node_modules/solr-client/.idea/.name

@ -0,0 +1 @@
solr-node-client

+ 11 - 0
src/doctor/node_modules/solr-client/.idea/inspectionProfiles/Project_Default.xml

@ -0,0 +1,11 @@
<component name="InspectionProjectProfileManager">
  <profile version="1.0">
    <option name="myName" value="Project Default" />
    <option name="myLocal" value="true" />
    <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
      <option name="processCode" value="true" />
      <option name="processLiterals" value="true" />
      <option name="processComments" value="true" />
    </inspection_tool>
  </profile>
</component>

+ 7 - 0
src/doctor/node_modules/solr-client/.idea/inspectionProfiles/profiles_settings.xml

@ -0,0 +1,7 @@
<component name="InspectionProjectProfileManager">
  <settings>
    <option name="PROJECT_PROFILE" value="Project Default" />
    <option name="USE_PROJECT_PROFILE" value="true" />
    <version value="1.0" />
  </settings>
</component>

+ 6 - 0
src/doctor/node_modules/solr-client/.idea/jsLibraryMappings.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="JavaScriptLibraryMappings">
    <file url="file://$PROJECT_DIR$" libraries="{solr-node-client node_modules}" />
  </component>
</project>

+ 14 - 0
src/doctor/node_modules/solr-client/.idea/libraries/solr_node_client_node_modules.xml

@ -0,0 +1,14 @@
<component name="libraryTable">
  <library name="solr-node-client node_modules" type="javaScript">
    <properties>
      <option name="frameworkName" value="node_modules" />
      <sourceFilesUrls>
        <item url="file://$PROJECT_DIR$/node_modules" />
      </sourceFilesUrls>
    </properties>
    <CLASSES>
      <root url="file://$PROJECT_DIR$/node_modules" />
    </CLASSES>
    <SOURCES />
  </library>
</component>

+ 13 - 0
src/doctor/node_modules/solr-client/.idea/misc.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectLevelVcsManager" settingsEditedManually="false">
    <OptionsSetting value="true" id="Add" />
    <OptionsSetting value="true" id="Remove" />
    <OptionsSetting value="true" id="Checkout" />
    <OptionsSetting value="true" id="Update" />
    <OptionsSetting value="true" id="Status" />
    <OptionsSetting value="true" id="Edit" />
    <ConfirmationsSetting value="0" id="Add" />
    <ConfirmationsSetting value="0" id="Remove" />
  </component>
</project>

+ 8 - 0
src/doctor/node_modules/solr-client/.idea/modules.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/.idea/solr-node-client.iml" filepath="$PROJECT_DIR$/.idea/solr-node-client.iml" />
    </modules>
  </component>
</project>

+ 9 - 0
src/doctor/node_modules/solr-client/.idea/solr-node-client.iml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
  <component name="NewModuleRootManager">
    <content url="file://$MODULE_DIR$" />
    <orderEntry type="inheritedJdk" />
    <orderEntry type="sourceFolder" forTests="false" />
    <orderEntry type="library" name="solr-node-client node_modules" level="project" />
  </component>
</module>

+ 6 - 0
src/doctor/node_modules/solr-client/.idea/vcs.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
  </component>
</project>

File diff suppressed because it is too large
+ 770 - 0
src/doctor/node_modules/solr-client/.idea/workspace.xml


+ 8 - 0
src/doctor/node_modules/solr-client/.npmignore

@ -0,0 +1,8 @@
node_modules
examples
docs
jshint
output.txt
coverage.html
lib-cov
report

+ 7 - 0
src/doctor/node_modules/solr-client/.travis.yml

@ -0,0 +1,7 @@
before_script: curl -sSL https://raw.githubusercontent.com/moliware/travis-solr/797595342d3b597d373c635329fad6bcf0378e3f/travis-solr.sh | SOLR_VERSION=4.9.1 bash
language: node_js
node_js:
  - '0.10'
  - '0.11'
  - '0.12'
  - '4.1.1'

+ 17 - 0
src/doctor/node_modules/solr-client/CONTRIBUTIONS.md

@ -0,0 +1,17 @@
# CONTRIBUTIONS
The library lives and prospers thanks to you. If you want to share some of your work, or if you have fixed some of the 
open issues, send a pull request with your code and the tests for it.
We're currently re-organizing the project organization, and as such we're starting to adopt a stricter commit guideline,
 very much inspired from that of [AngularJS](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit)
So we ask you to respect 2 rules: 
1. Provide the code, the tests, all respecting the style guide
2. Follow AngularJS commit style-guide that can, at the most minimum, be summarized by a single line like:
 `feat(solrCloud): Add ability to query pivot per field`
 
Note: Please restrain from committing code that does other changes than what they are committed for. Avoid changing the 
style guide of entire files while committing fixes or features.
*Thanks in advance for your support in making the library live.*

+ 22 - 0
src/doctor/node_modules/solr-client/LICENCE

@ -0,0 +1,22 @@
(The MIT License)
Copyright 2011-2012 HipSnip Limited
Copyright 2013-2014 Rémy Loubradou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large
+ 213 - 0
src/doctor/node_modules/solr-client/README.md


+ 644 - 0
src/doctor/node_modules/solr-client/lib/collection.js

@ -0,0 +1,644 @@
/**
 * Load dependencies
 */
var querystring = require('querystring'),
    format = require('./utils/format');
/**
 * Expose `Collection`
 */
module.exports = exports = Collection;
/**
 * Create a new `Collection`
 * @constructor
 *
 * @return {Collection}
 * @api private
 */
function Collection(){
  this.parameters = [];
}
/**
 * Set a new parameter
 * Since all possibilities provided by Solr are not available in the `Collection` object, `set()` is there to fill this gap.
 *
 * @param {String} parameter - string, special characters have to be correctly encoded or the request will fail.
 *
 * @return {Collection} - allow chaining
 * @api public
 */
Collection.prototype.set = function(parameter){
  var self = this;
  this.parameters.push(parameter);
  return self;
}
/**
 * Create a new Collection
 *
 * @param {Object} options - Set of options for creating a new collection
 * @param {String} name - The name of the collection to be created
 * @param {String} [routerName] - The router name that will be used. Default is 'compositeId'. 'implicit' is the only other valid option.
 * @param {Number} [numShards] - Number of shards to be created as part of the collection. Required when using 'compositeId' router.
 * @param {String|Array} [shards] - A comma separated list of shard names, or an array of shard names. Required if using 'implicit' router.
 * @param {Number} [replicationFactor] - The number of replicas to be created for each shard.
 * @param {Number} [maxShardsPerNode] - Sets a limit on the number of replicas CREATE will spread to each node.
 * @param {String|Array} [createNodeSet] - Defines the nodes to spread the shards/ replicas across. Comma separated list of node names, or an array of node names.
 * @param {Boolean} [createNodeSetShuffle] - Controls whether or not the shard-replicas created will be assigned to the nodes in createNodeSet in a sequential or shuffled order.
 * @param {String} [collectionConfigName] - Defines name of the configurations to use for this collection. Must already be stored in ZooKeeper.
 * @param {String} [routerField] - If specified, router will look at the value of the field in an input document to compute the hash and identify a shard instead of looking at the uniqueKey field.
 * @param {Boolean} [autoAddReplicas] - When set to true, enables auto addition of replicas on shared file systems.
 * @param {String} [async] - Request ID to track this action which will be processed asynchonously.
 * @return {Collection}
 * @api public
 */
Collection.prototype.create = function(options) {
  var self = this;
  this.parameters.push('action=CREATE');
  if(options.name){
    this.parameters.push('name=' + options.name);
  }
  if(options.routerName){
    this.parameters.push('router.name=' + options.routerName);
  }
  if(options.numShards !== undefined){
    this.parameters.push('numShards=' + options.numShards);
  }
  if(options.shards !== undefined){
    if( typeof(options.shards) === 'string') {
      this.parameters.push('shards=' + options.shards);
    }else{
      this.parameters.push('shards=' + options.shards.join());
    }
  }
  if(options.replicationFactor !== undefined){
    this.parameters.push('replicationFactor=' + options.replicationFactor);
  }
  if(options.maxShardsPerNode !== undefined){
    this.parameters.push('maxShardsPerNode=' + options.maxShardsPerNode);
  }
  if(options.createNodeSet !== undefined){
    if( typeof(options.createNodeSet) === 'string') {
      this.parameters.push('createNodeSet=' + options.createNodeSet);
    }else{
      this.parameters.push('createNodeSet=' + options.createNodeSet.join());
    }
  }
  if(options.createNodeSetShuffle !== undefined){
    this.parameters.push('createNodeSet.shuffle=' + options.createNodeSetShuffle);
  }
  if(options.collectionConfigName){
    this.parameters.push('collection.configName=' + options.collectionConfigName);
  }
  if(options.routerField){
    this.parameters.push('router.field=' + options.routerField);
  }
  if(options.autoAddReplicas !== undefined){
    this.parameters.push('autoAddReplicas=' + options.autoAddReplicas);
  }
  if(options.async){
    this.parameters.push('async=' + options.async);
  }
  return self;
}
/**
 * Reload a Collection
 * 
 * @param {String} name - Name of the Collection to be reloaded
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.reload = function(name) {
  var self = this;
  this.parameters.push('action=RELOAD');
  if(name){
    this.parameters.push('name=' + name);
  }
  return self;
}
/**
 * Split a shard
 * 
 * @param {Object} options - Options for splitting the shard
 * @param {String} collection - Name of the Collection that includes the shard to be split
 * @param {String} shard - Name of the shard to be split
 * @param {String|Array} [ranges] - Comma separated list of hash ranges in hexadecimal. If an array is supplied, it will be joined with commas.
 * @param {String} [splitKey] - The key to use for splitting the index
 * @param {String} [async] - Request ID to track this action, processed asynchonously.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.splitShard = function(options) {
  var self = this;
  this.parameters.push('action=SPLITSHARD');
  if(options.collection){
    this.parameters.push('collection=' + options.collection);
  }
  if(options.shard){
    this.parameters.push('shard=' + options.shard);
  }
  if(options.ranges !== undefined){
    if(typeof(ranges) === 'string'){
      this.parameters.push('ranges=' + options.ranges);
    }else{
      this.parameters.push('ranges=' + options.ranges.join());
    }
  }
  if(options.splitKey){
    this.parameters.push('split.key=' + options.splitKey);
  }
  if(options.async){
    this.parameters.push('async=' + options.async);
  }
  return self;
}
/**
 * Create a shard
 * Can only be used for collections that use the 'implicit' router. Use SPLITSHARD for the `compositId` router.
 * 
 * @param {Object} options - Options for creating the shard
 * @param {String} collection - Name of the Collection where the shard should be created
 * @param {String} shard - Name of the shard to be created
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.createShard = function(options) {
  var self = this;
  this.parameters.push('action=CREATESHARD');
  if(options.collection){
    this.parameters.push('collection=' + options.collection);
  }
  if(options.shard){
    this.parameters.push('shard=' + options.shard);
  }
  return self;
}
/**
 * Delete a shard
 * 
 * @param {Object} options - Options for deleting the shard
 * @param {String} collection - Name of the Collection that includes the shard to be deleted
 * @param {String} shard - Name of the shard to be deleted
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.deleteShard = function(options) {
  var self = this;
  this.parameters.push('action=DELETESHARD');
  if(options.collection){
    this.parameters.push('collection=' + options.collection);
  }
  if(options.shard){
    this.parameters.push('shard=' + options.shard);
  }
  return self;
}
/**
 * Create/ Modify alias for a collection
 * 
 * @param {Object} options - Options for creation of the collection alias.
 * @param {String} name - The alias name to be created.
 * @param {String|Array} collections - A comma separated list of collections to be aliased. If an array is provided, it will be joined by commas.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.createAlias = function(options) {
  var self = this;
  this.parameters.push('action=CREATEALIAS');
  if(options.name){
    this.parameters.push('name=' + options.name);
  }
  if(options.collections !== undefined){
    if(typeof(options.collections) === 'string'){
      this.parameters.push('collections=' + options.collections);
    } else{
      this.parameters.push('collections=' + options.collections.join());
    }
  }
  return self;
}
/**
 * Delete a collection alias
 * 
 * @param {String} name - Name of the alias to delete
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.deleteAlias = function(name) {
  var self = this;
  this.parameters.push('action=DELETEALIAS');
  if(name){
    this.parameters.push('name=' + name);
  }
  return self;
}
/**
 * Delete a Collection
 *
 * @param {String} name - The name of the collection to be deleted
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.delete = function(name) {
  var self = this;
  this.parameters.push('action=DELETE');
  if(name){
    this.parameters.push('name=' + name);
  }
  return self;
}
/**
 * Delete a replica
 * 
 * @param {Object} options - Options for deleting the replica
 * @param {String} collection - Name of the Collection that includes the replica to be deleted
 * @param {String} shard - Name of the shard that includes the replica to be deleted
 * @param {String} replica - The name of the replica to remove.
 * @param {Boolean} [onlyIfDown] - If true, deletion will only execute if the replica is down/ not active.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.deleteReplica = function(options) {
  var self = this;
  this.parameters.push('action=DELETEREPLICA');
  if(options.collection){
    this.parameters.push('collection=' + options.collection);
  }
  if(options.shard){
    this.parameters.push('shard=' + options.shard);
  }
  if(options.replica){
    this.parameters.push('replica=' + options.replica);
  }
  if(options.onlyIfDown !== undefined){
    this.parameters.push('onlyIfDown=' + options.onlyIfDown);
  }
  return self;
}
/**
 * Add replica
 * 
 * @param {Object} options - Options for adding the replica
 * @param {String} collection - Name of the Collection
 * @param {String} shard - Name of the shard to which the replica will be added
 * @param {String} [route] - If the exact shard name is not known, route can be passed and the system will identify the shard. Ignored if shard is specified.
 * @param {String} [node] - Name of the node where the replica should be created.
 * @param {String} [async] - Request ID to track this action, which will be processed asynchronously.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.addReplica = function(options) {
  var self = this;
  this.parameters.push('action=ADDREPLICA');
  if(options.collection){
    this.parameters.push('collection=' + options.collection);
  }
  if(options.shard){
    this.parameters.push('shard=' + options.shard);
  }
  if(options.route){
    this.parameters.push('_route_=' + options.route);
  }
  if(options.node){
    this.parameters.push('node=' + options.node);
  }
  if(options.async){
    this.parameters.push('async=' + options.async);
  }
  return self;
}
/**
 * Cluster Properties
 * 
 * @param {Object} options - Options for cluster properties
 * @param {String} name - Name of the property. Two supported properties are 'urlScheme' and 'autoAddReplicas.' Others will be rejected by Solr.
 * @param {String} val - Value of the property. If the value is empty or null, the property is unset.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.clusterProp = function(options) {
  var self = this;
  this.parameters.push('action=CLUSTERPROP');
  if(options.name){
    this.parameters.push('name=' + options.name);
  }
  if(options.val !== undefined){
    this.parameters.push('val=' + options.val);
  }
  return self;
}
/**
 * Migrate documents to another collection
 * 
 * @param {Object} options - Options for document migration
 * @param {String} collection - Name of the source collection from which documents will be split.
 * @param {String} targetCollection - Name of the target collection to which documents will be migrated.
 * @param {String} splitKey - The routing key prefix.
 * @param {Number} [forwardTimeout] - The timeout, in seconds, until which write requests made to the source collection for the given split.key will be forwarded to the target shard. Default is 60 seconds.
 * @param {String} [async] - Request ID to track this action which will be processed asynchronously.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.migrate = function(options) {
  var self = this;
  this.parameters.push('action=MIGRATE');
  if(options.collection){
    this.parameters.push('collection=' + options.collection);
  }
  if(options.targetCollection){
    this.parameters.push('target.collection=' + options.targetCollection);
  }
  if(options.splitKey){
    this.parameters.push('split.key=' + options.splitKey);
  }
  if(options.forwardTimeout !== undefined){
    this.parameters.push('forward.timeout=' + options.forwardTimeout);
  }
  if(options.async){
    this.parameters.push('async=' + options.async);
  }
  return self;
}
/**
 * Add Role
 * 
 * @param {Object} options - Options for adding the role
 * @param {String} role - Name of role. Only current supported role is 'overseer'
 * @param {String} node - Name of the node.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.addRole = function(options) {
  var self = this;
  this.parameters.push('action=ADDROLE');
  if(options.role){
    this.parameters.push('role=' + options.role);
  }
  if(options.node){
    this.parameters.push('node=' + options.node);
  }
  return self;
}
/**
 * Remove Role
 * Undo roles assigned using ADDROLE operation
 * 
 * @param {Object} options - Options for removing the role
 * @param {String} role - Name of role. Only current supported role is 'overseer'
 * @param {String} node - Name of the node.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.removeRole = function(options) {
  var self = this;
  this.parameters.push('action=REMOVEROLE');
  if(options.role){
    this.parameters.push('role=' + options.role);
  }
  if(options.node){
    this.parameters.push('node=' + options.node);
  }
  return self;
}
/**
 * Overseer status and statistics
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.overseerStatus = function() {
  var self = this;
  this.parameters.push('action=OVERSEERSTATUS');
  return self;
}
/**
 * Cluster status
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.clusterStatus = function() {
  var self = this;
  this.parameters.push('action=CLUSTERSTATUS');
  return self;
}
/**
 * Request status
 * request the status of an already submitted Asynchronous Collection API call.
 *
 * @param {String} requestid - User-defined request ID from the submitted API call. A value of '-1' will clear the stored states for the already completed/ failed tasks.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.requestStatus = function(requestid) {
  var self = this;
  this.parameters.push('action=REQUESTSTATUS');
  if(requestid){
    this.parameters.push('requestid=' + requestid);
  }
  return self;
}
/**
 * List collections
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.list = function() {
  var self = this;
  this.parameters.push('action=LIST');
  return self;
}
/**
 * Add Replica property
 * 
 * @param {Object} options - Options for replica property
 * @param {String} collection - Name of collection this replica belongs to.
 * @param {String} shard - Name of the shard the replica belongs to.
 * @param {String} replica - The name of the replica
 * @param {String} property - The property to add
 * @param {String} propertyValue - The value to assign to the property.
 * @param {Boolean} [shardUnique] - If set to true, then setting this property in one replica will remove the property from all other replicas in that shard.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.addReplicaProp = function(options) {
  var self = this;
  this.parameters.push('action=ADDREPLICAPROP');
  if(options.collection){
    this.parameters.push('collection=' + options.collection);
  }
  if(options.shard){
    this.parameters.push('shard=' + options.shard);
  }
  if(options.replica){
    this.parameters.push('replica=' + options.replica);
  }
  if(options.property){
    this.parameters.push('property=' + options.property);
  }
  if(options.propertyValue !== undefined){
    this.parameters.push('property.value=' + options.propertyValue);
  }
  if(options.shardUnique !== undefined){
    this.parameters.push('shardUnique=' + options.shardUnique);
  }
  return self;
}
/**
 * Delete Replica property
 * 
 * @param {Object} options - Options for replica property
 * @param {String} collection - Name of collection this replica belongs to.
 * @param {String} shard - Name of the shard the replica belongs to.
 * @param {String} replica - The name of the replica
 * @param {String} property - The property to remove
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.deleteReplicaProp = function(options) {
  var self = this;
  this.parameters.push('action=DELETEREPLICAPROP');
  if(options.collection){
    this.parameters.push('collection=' + options.collection);
  }
  if(options.shard){
    this.parameters.push('shard=' + options.shard);
  }
  if(options.replica){
    this.parameters.push('replica=' + options.replica);
  }
  if(options.property){
    this.parameters.push('property=' + options.property);
  }
  return self;
}
/**
 * Balance a property
 * 
 * @param {Object} options - Options for replica property
 * @param {String} collection - Name of collection to balance the property in.
 * @param {String} property - The property to balance
 * @param {Boolean} [onlyActiveNodes] - Default is true. When true, property is instantiated on active nodes only. If false, inactive nodes will be included.
 * @param {Boolean} [shardUnique] - If set to true, then setting this property in one replica will r
emove the property from all other replicas in that shard.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.balanceShardUnique = function(options) {
  var self = this;
  this.parameters.push('action=BALANCESHARDUNIQUE');
  if(options.collection){
    this.parameters.push('collection=' + options.collection);
  }
  if(options.property){
    this.parameters.push('property=' + options.property);
  }
  if(options.onlyActiveNodes !== undefined){
    this.parameters.push('onlyActiveNodes=' + options.onlyActiveNodes);
  }
  if(options.shardUnique !== undefined){
    this.parameters.push('shardUnique=' + options.shardUnique);
  }
  return self;
}
/**
 * Rebalance leaders
 * 
 * @param {Object} options - Options for replica property
 * @param {String} collection - Name of collection to rebalance preferredLeaders on.
 * @param {Number} [maxAtOnce] - The maximum number of reassignments to have queue up at once.
 * @param {Number} [maxWaitSeconds] - Timeout value (seconds) when waiting for leaders to be reassigned. Default is 60.
 *
 * @return {Collection}
 * @api public
 */
Collection.prototype.rebalanceLeaders = function(options) {
  var self = this;
  this.parameters.push('action=REBALANCELEADERS');
  if(options.collection){
    this.parameters.push('collection=' + options.collection);
  }
  if(options.maxAtOnce !== undefined){
    this.parameters.push('maxAtOnce=' + options.maxAtOnce);
  }
  if(options.maxWaitSeconds !== undefined){
    this.parameters.push('maxWaitSeconds=' + options.maxWaitSeconds);
  }
  return self;
}
/**
 * Build a querystring with the array of `this.parameters`.
 *
 * @return {String}
 * @api private
 */
Collection.prototype.build = function(){
  return this.parameters.join('&');
}

+ 49 - 0
src/doctor/node_modules/solr-client/lib/error/solr-error.js

@ -0,0 +1,49 @@
/**
 * Module dependencies
 */
var HTTPError = require('httperror'),
   util = require('util');
/**
 * Expose `SolrError`
 */
module.exports = SolrError;
/**
 * Create a new `SolrError`
 * @constructor
 *
 * @return {SolrError}
 * @api private
 */
function SolrError(req,res,htmlMessage){
   var message = '';
   if(htmlMessage){
      var matches = htmlMessage.match(/<pre>([\s\S]+)<\/pre>/);
      message = decode((matches || ['', htmlMessage])[1].trim());
   }
   HTTPError.call(this, req, res, message);
   Error.captureStackTrace(this,arguments.callee);
   this.name = 'SolrError';
}
util.inherits(SolrError, HTTPError);
/**
 * Decode few HTML entities: &<>'"
 *
 * @param {String} str -
 *
 * @return {String}
 * @api private
 */
function decode(str) {
  return str.replace(/&amp;/g,'&')
            .replace(/&lt;/gm, '<')
            .replace(/&gt;/gm, '>')
            .replace(/&apos;/gm, '\'')
            .replace(/&quot;/gm, '"');
};

+ 861 - 0
src/doctor/node_modules/solr-client/lib/query.js

@ -0,0 +1,861 @@
/**
 * Load dependencies
 */
var querystring = require('querystring'),
    format = require('./utils/format'),
    arrayUtils = require('./utils/array'),
    versionUtils = require('./utils/version');
/**
 * Expose `Query`
 */
module.exports = exports = Query;
/**
 * Create a new `Query`
 * @constructor
 *
 * @return {Query}
 * @api private
 */
function Query(options){
   this.solrVersion = (options && options.solrVersion) || undefined;
   this.parameters = [];
}
/**
 * Set a new parameter
 * Since all possibilities provided by Solr are not available in the `Query` object, `set()` is there to fit this gap.
 *
 * @param {String} parameter - string, special characters have to be correctly encoded or the request will fail.
 *
 * @return {Query} - allow chaining
 * @api public
 */
Query.prototype.set = function(parameter){
   var self = this;
   this.parameters.push(parameter);
   return self;
}
/**
 * Set the query parser to use with this request.
 *
 * @param {String} type - name of the query parser e.g: 'dismax'
 *
 * @return {Query}
 * @api public
 */
Query.prototype.defType = function(type){
   var self = this;
   var parameter = 'defType=' + type;
   this.parameters.push(parameter);
   return self;
}
/**
 * Set the Request Handler used to process the request based on its `name`.
 * Works only if no Request Handler has been configured with `/select` as its name in solrconfig.xml.
 *
 * @param {String} name - name of the Request Handler
 *
 * @return {Query}
 * @api public
 */
Query.prototype.requestHandler =
Query.prototype.qt = function(name){
  var self = this;
  var parameter = 'qt=' + name;
  this.parameters.push(parameter);
  return self;
}
/**
 *  Set the main query
 *
 * @param {String|Object} q -
 *
 * @return  {Query}
 * @api public
 */
Query.prototype.q = function(q){
   var self = this;
   var parameter ='q=';
   if ( typeof(q) === 'string' ){
      parameter += encodeURIComponent(q);
   }else{
      parameter += querystring.stringify(q, '%20AND%20',':');
   }
   this.parameters.push(parameter);
   return self;
}
/**
 *  Set the default query operator
 *
 * @param {String} op -
 *
 * @return  {Query}
 * @api public
 */
Query.prototype.qop = function(op){
    var self = this;
    var parameter ='q.op=';
    parameter += op;
    this.parameters.push(parameter);
    return self;
};
/**
 * Set the default query field.
 *
 * @param {String} df - the default field where solr should search.
 *
 * @return  {Query}
 * @api public
 */
Query.prototype.df = function (df) {
    var self = this;
    var parameter = 'df=';
    parameter += df;
    this.parameters.push(parameter);
    return self;
};
/**
 * Set the offset where the set of returned documents should begin.
 *
 * @param {Number} start - the offset where the set of returned documents should begin.
 *
 * @return {Query}
 * @api public
 */
Query.prototype.start = function(start){
   var self = this;
   var parameter = 'start=' + start ;
   this.parameters.push(parameter);
   return self;
}
/**
 * Set the maximum number of documents returned
 *
 * @param {Number} rows - number of documents
 *
 * @return {Query}
 * @api public
 */
Query.prototype.rows = function(rows){
   var self = this;
   var parameter = 'rows=' + rows ;
   this.parameters.push(parameter);
   return self;
}
/**
 * Request to use cursorMarks for deep-paging as explained in http://heliosearch.org/solr/paging-and-deep-paging/
 * Note that usage of a cursor requires a sort containing a uniqueKey field tie breaker
 *
 * @param {String} mark - The mark to use, defaults to "*" to request a new cursor in the first request
 *
 * @return {Query}
 * @api public
 */
Query.prototype.cursorMark = function(mark){
   var self = this;
   mark = mark || "*";
   var parameter = 'cursorMark=' + mark ;
   this.parameters.push(parameter);
   return self;
}
/**
 * Sort a result in descending or ascending order based on one or more fields.
 *
 * @param {Object} options -
 *
 * @return {Query}
 * @api public
 */
Query.prototype.sort = function(options){
   var self = this;
   var parameter = 'sort=';
   parameter += querystring.stringify(options, ',' , '%20');
   this.parameters.push(parameter);
   return self;
}
/**
 * Filter the set of documents found before to return the result with the given range determined by `field`, `start` and `end`.
 *
 * @param {Array|Object} options -
 * @param {String} options.field - the name of the field where the range is applied
 * @param {String|Number|Date} options.start - the offset where the range starts
 * @param {String|Number|Date} options.end - the offset where the range ends
 *
 * @return {Query}
 * @api public
 *
 * @example
 * var query = client.createQuery();
 * query.q({ '*' : '*' }).rangeFilter({ field : 'id', start : 100, end : 200})
 * // also works
 * query.q({ '*' : '*' }).rangeFilter([{ field : 'id', start : 100, end : 200},{ field : 'date', start : new Date(), end : new Date() - 3600}]);
 */
Query.prototype.rangeFilter = function(options){
   var self = this;
   options = format.dateISOify(options);
   var parameter = 'fq=';
   if(Array.isArray(options)){
     parameter += "(";
      var filters = options.map(function(option){
         var key = option.field;
         var filter = {};
         filter[key] = '[' + encodeURIComponent(option.start) + '%20TO%20' + encodeURIComponent(option.end) + ']';
         return format.stringify(filter, '',':');
      });
      parameter += filters.join('%20AND%20');
      parameter += ")";
   }else{
      var key = options.field;
      var filter = {};
      filter[key] = '[' + encodeURIComponent(options.start) + '%20TO%20' + encodeURIComponent(options.end) + ']';
      parameter += format.stringify(filter, '',':');
   }
   this.parameters.push(parameter);
   return self;
}
/**
 * Filter the set of documents found before to return the result with the given `field` and `value`.
 *
 * @param {String} field - name of field
 * @param {String|Number|Date} value - value of the field that must match
 *
 * @return {Query}
 * @api public
 *
 * @example
 * var query = client.createQuery();
 * query.q({ '*' : '*' }).matchFilter('id', 100)
 */
Query.prototype.matchFilter = function(field,value){
   var self = this;
   value = format.dateISOify(value);
   var parameter = 'fq=';
   parameter += field + ':' + encodeURIComponent(value);
   this.parameters.push(parameter);
   return self;
}
/**
 * Specify a set of fields to return.
 *
 * @param {String|Array} field - field name
 *
 * @return {Query}
 * @api public
 */
Query.prototype.fl =
Query.prototype.restrict = function(fields){
   var self = this;
   var parameter = 'fl=';
   if(typeof(fields) === 'string'){
      parameter += fields;
   }else{
      parameter += fields.join(',');
   }
   this.parameters.push(parameter);
   return self;
}
/**
 * Set the time allowed for a search to finish.
 * Partial results may be returned (if there are any).
 *
 * @param {String|Number} time - time is in milliseconds. Values <= 0 mean no time restriction.
 *
 * @return {Query}
 * @api public
 */
Query.prototype.timeout = function(time){
   var self = this;
   var parameter = 'timeAllowed=' + time;
   this.parameters.push(parameter);
   return self;
}
/**
 * Group documents with the given `field`
 *
 * @param {String} field - field name
 *
 * @return {Query}
 * @api public
 */
Query.prototype.groupBy = function(field){
   var self = this;
   this.group({
      'field': field
   });
   return self;
}
/**
 * Group documents using field collapsing or result grouping feature.
 * Field Collapsing collapses a group of results with the same field value down to a single (or fixed number) of entries.
 * Result Grouping groups documents with a common field value into groups, returning the top documents per group, and the top groups based on what documents are in the groups.
 *
 * @param {Object} options
 * @param {Boolean} [options.on=true] - if false, turn off result grouping, otherwise turn on.
 * @param {String|Array} options.field - Group based on the unique values of a field.
 * @param {Number} [options.limit=1] - The number of results (documents) to return for each group. Solr's default value is 1.
 * @param {Number} options.offset - The offset into the document list of each group.
 * @param {String} [options.sort="score desc"] - How to sort documents within a single group. Defaults to the same value as the sort parameter.
 * @param {String} options.format - if simple, the grouped documents are presented in a single flat list. The start and rows parameters refer to numbers of documents instead of numbers of groups.
 * @param {Boolean} options.main - If true, the result of the last field grouping command is used as the main result list in the response, using group.format=simple.
 * @param {Boolean} [options.ngroups=false] - If true, includes the number of groups that have matched the query. Default is false.
 * @param {Boolean} options.truncate - If true, facet counts are based on the most relevant document of each group matching the query. Same applies for StatsComponent. Default is false.
 * @param {Number}  [options.cache=0] - If > 0 enables grouping cache. Grouping is executed actual two searches. This option caches the second search. A value of 0 disables grouping caching. Default is 0.
 *
 * @return {Query}
 * @api public
 */
Query.prototype.group = function(options){
   var self = this;
   if(options.on === false){
      this.parameters.push('group=false');
   }else{
      this.parameters.push('group=true');
   }
   if( options.field ){
      options.field = arrayUtils.toArray(options.field);
      options.field.forEach(function(field){
        self.parameters.push('group.field=' + field);
      });
   }
   if( options.limit !== undefined){
      this.parameters.push('group.limit=' + options.limit);
   }
   if( options.offset !== undefined){
      this.parameters.push('group.offset=' + options.offset);
   }
   if( options.sort ){
      this.parameters.push('group.sort=' + encodeURIComponent(options.sort));
   }
   if( options.format ){
      this.parameters.push('group.format=' + encodeURIComponent(options.format));
   }
   if( options.main !== undefined){
      this.parameters.push('group.main=' + options.main);
   }
   if( options.ngroups !== undefined){
      this.parameters.push('group.ngroups=' + options.ngroups);
   }
   if( options.truncate !== undefined){
      this.parameters.push('group.truncate=' + options.truncate);
   }
   if( options.cache !== undefined){
      this.parameters.push('group.cache.percent=' + options.cache);
   }
   return self;
}
/**
 * Create a facet
 *
 * @param {Object} options - set of options to create a facet
 * @param {Boolean} [options.on=true] - Turn on or off facet
 * @param {String} [options.query] - This parameter allows you to specify an arbitrary query in the Lucene default syntax to generate a facet count. By default, faceting returns a count of the unique terms for a "field", while facet.query allows you to determine counts for arbitrary terms or expressions.
 * @param {String|Array} options.field - This parameter allows you to specify a field which should be treated as a facet. It will iterate over each Term in the field and generate a facet count using that Term as the constraint. Multiple fields can be defined providing an array instead of a string.
 * @param {String} [options.prefix] - Limits the terms on which to facet to those starting with the given string prefix.
 * @param {String} [options.sort] - This param determines the ordering of the facet field constraints.count
 * @param {Number} [options.limit=100] - This parameter indicates the maximum number of constraint counts that should be returned for the facet fields. A negative value means unlimited.The solr's default value is 100.
 * @param {Number} [options.offset=0] - This param indicates an offset into the list of constraints to allow paging.The solr's default value is 0.
 * @param {Number} [options.mincount=0] - This parameter indicates the minimum counts for facet fields should be included in the response. The solr's default value is 0.
 * @param {Boolean} [options.missing=false] - Set to `true` this param indicates that in addition to the Term based constraints of a facet field, a count of all matching results which have no value for the field should be computed. The solr's default value is false.
 * @param {String} [options.method="fc"] - This parameter indicates what type of algorithm/method to use when faceting a field.The solr's default value is fc (except for BoolField).
 * @param {String|Array} options.pivot - This parameter allows you to specify a field which should be treated as a facet pivot. It will iterate over each Term in the field. Multiple fields can be defined providing an array instead of a string.
 * @param {String} [options.pivot.mincount=0] - This parameter indicates the minimum counts for facet pivot fields to be included in the response. The solr's default value is 0.
 *
 * @return {Query}
 * @api public
 */
Query.prototype.facet = function(options){
   var self = this;
   if(options.on === false){
      this.parameters.push('facet=false');
   }else{
      this.parameters.push('facet=true');
   }
   if(options.query){
      this.parameters.push('facet.query=' + encodeURIComponent(options.query))
   }
   if(options.field){
     options.field = arrayUtils.toArray(options.field);
     options.field.forEach(function(field) {
       self.parameters.push('facet.field=' + field);
     });
   }
   if(options.prefix){
      this.parameters.push('facet.prefix=' + encodeURIComponent(options.prefix))
   }
   if(options.sort){
      this.parameters.push('facet.sort=' + encodeURIComponent(options.sort))
   }
   if(options.limit !== undefined){
      this.parameters.push('facet.limit=' + options.limit);
   }
   if(options.offset !== undefined){
      this.parameters.push('facet.offset=' + options.offset);
   }
   if(options.mincount !== undefined){
      this.parameters.push('facet.mincount=' + options.mincount);
   }
   if(options.missing !== undefined){
      this.parameters.push('facet.missing=' + options.missing);
   }
   if(options.method){
      this.parameters.push('facet.method=' + options.method);
   }
   // Only supported with version 4.0 and above
   if(this.solrVersion && (versionUtils.version(this.solrVersion) >= versionUtils.Solr4_0)) {
     if(options.pivot){
       options.field = arrayUtils.toArray(options.pivot.fields);
       options.field.forEach(function(field) {
         self.parameters.push('facet.pivot=' + field);
       });
     }
     if(options.pivot.mincount) {
       this.parameters.push('facet.pivot.mincount=' + options.pivot.mincount);
     }
   }
   return self;
}
/**
 * Create a MoreLikeThis. MoreLikeThis constructs a lucene query based on terms within a document.
 *
 * @param {Object} options - set of options to create a morelikethis
 * @param {Boolean} [options.on=true] - Turn on or off morelikethis
 * @param {String|Array} [options.fl] - The fields to use for similarity. NOTE: if possible, these should have a stored TermVector
 * @param {Number} [options.count] - The number of similar documents to return for each result.
 * @param {Number} [options.mintf] - Minimum Term Frequency - the frequency below which terms will be ignored in the source doc.
 * @param {Number} [options.mindf] - Minimum Document Frequency - the frequency at which words will be ignored which do not occur in at least this many docs.
 * @param {Number} [options.minwl] - minimum word length below which words will be ignored.
 * @param {Number} [options.maxwl] - maximum word length above which words will be ignored.
 * @param {Number} [options.maxqt] - maximum number of query terms that will be included in any generated query.
 * @param {Number} [options.maxntp] - maximum number of tokens to parse in each example doc field that is not stored with TermVector support.
 * @param {Boolean} [options.boost] - set if the query will be boosted by the interesting term relevance.
 * @param {String|Object} [options.qf] - Query fields and their boosts using the same format as that used in DisMaxQParserPlugin. These fields must also be specified in mlt.fl.
 *
 * @return {Query}
 * @api public
 */
Query.prototype.mlt = function(options){
  var self = this;
  if(options.on === false){
    this.parameters.push('mlt=false');
  }else{
    this.parameters.push('mlt=true');
  }
  if(options.fl){
    if(options.fl instanceof Array) options.fl = options.fl.join(',');
    this.parameters.push('mlt.fl=' + encodeURIComponent(options.fl))
  }
  if(options.count !== undefined){
    this.parameters.push('mlt.count=' + options.count)
  }
  if(options.mintf !== undefined){
    this.parameters.push('mlt.mintf=' + options.mintf)
  }
  if(options.mindf !== undefined){
    this.parameters.push('mlt.mindf=' + options.mindf);
  }
  if(options.minwl !== undefined){
    this.parameters.push('mlt.minwl=' + options.minwl)
  }
  if(options.maxwl !== undefined ){
    this.parameters.push('mlt.maxwl=' + options.maxwl)
  }
  if(options.maxqt !== undefined){
    this.parameters.push('mlt.maxqt=' + options.maxqt)
  }
  if(options.maxntp !== undefined){
    this.parameters.push('mlt.maxntp=' + options.maxntp);
  }
  if(options.boost !== undefined){
    this.parameters.push('mlt.boost=' + options.boost);
  }
  if(options.qf){
    if( typeof options.qf === 'object'){
      var parameter = querystring.stringify(options.qf, '%20' , '^');;
    }else{
      var parameter = encodeURIComponent(options.qf);
    }
    this.parameters.push('mlt.qf=' + parameter);
  }
  return self;
}
/*!
 * DisMax parameters
 * do not forget to use `.dismax()` when using these parameters
 */
/**
 * Use the DisMax query parser
 *
 * @return {Query}
 * @api public
 */
Query.prototype.dismax = function(){
   var self = this;
   this.defType('dismax');
   return self;
}
/*!
 * EDisMax parameters
 * do not forget to use `.edismax()` when using these parameters
 */
/**
 * Use the EDisMax query parser
 *
 * @return {Query}
 * @api public
 */
Query.prototype.edismax = function(){
   var self = this;
   this.defType('edismax');
   return self;
}
/**
 * Add the parameter debugQuery.
 * Additional debugging informations will be available in the response.
 *
 * @return {Query}
 * @api public
 */
Query.prototype.debugQuery = function(){
   var self = this;
   this.parameters.push('debugQuery=true');
   return self;
}
//TODO
Query.prototype.ps = function(){}
/**
 * Set the "boosts" to associate with each fields
 *
 * @param {Object} options -
 *
 * @return {Query}
 * @api public
 *
 * @example
 * var query = client.createQuery();
 * query.qf({title : 2.2, description : 0.5 });
 */
Query.prototype.qf = function(options){
   var self = this;
   var parameter = 'qf=' ;
   parameter += querystring.stringify(options, '%20' , '^');
   this.parameters.push(parameter);
   return self;
}
/**
 * Set the minimum number or percent of clauses that must match.
 *
 * @param {String|Number} minimum - number or percent of clauses that must match
 *
 * @return {Query}
 * @api public
 *
 * @example
 * var query = client.createQuery();
 * query.mm(2); // or query.mm('75%');
 */
Query.prototype.mm = function(minimum){
   var self = this;
   var parameter = 'mm=' + minimum;
   this.parameters.push(parameter);
   return self;
}
/**
 * Set the Phrase Fields parameter.
 * Once the list of matching documents has been identified using the "fq" and "qf" params, the "pf" param can be used to "boost" the score of documents in cases where all of the terms
 * in the "q" param appear in close proximity.
 *
 * @param {Object} options -
 *
 * @return {Query}
 * @api public
 */
Query.prototype.pf = function(options){
   var self = this;
   var parameter = 'pf=' ;
   parameter += querystring.stringify(options, '%20' , '^');
   this.parameters.push(parameter);
   return self;
}
/**
 * Set the phrase slop allowed in a query.
 *
 * @param {Number} slop - Amount of phrase slop allowed by the query filter. This value should represent the maximum number of words allowed between words in a field that match a phrase in the query.
 *
 * @return {Query}
 * @api public
 */
Query.prototype.ps = function(slop){
   var self = this;
   var parameter = 'ps=' + slop;
   this.parameters.push(parameter);
   return self;
};
/**
 * Set the query slop allowed in a query.
 *
 * @param {Number} slop - Amount of query slop allowed by the query filter. This value should be used to affect boosting of query strings.
 *
 * @return {Query}
 * @api public
 */
Query.prototype.qs = function(slop){
   var self = this;
   var parameter = 'qs=' + slop;
   this.parameters.push(parameter);
   return self;
};
/**
 * Set the tiebreaker in DisjunctionMaxQueries (should be something much less than 1)
 *
 * @param {Float|Number} tiebreaker -
 *
 * @return {Query}
 * @api public
 */
Query.prototype.tie = function(tiebreaker){
   var self = this;
   var parameter = 'tie=' + tiebreaker;
   this.parameters.push(parameter);
   return self;
}
/**
 * Set the Boost Query parameter.
 * A raw query string (in the SolrQuerySyntax) that will be included with the user's query to influence the score. If this is a BooleanQuery with a default boost (1.0f) then the individual clauses will be added directly to the main query. Otherwise, the query will be included as is.
 *
 * @param {Object} options -
 *
 * @return {Query}
 * @api public
 */
Query.prototype.bq = function(options){
   var self = this;
   var parameter = 'bq=' ;
   parameter += querystring.stringify(options, '%20' , '^');
   this.parameters.push(parameter);
   return self;
}
/**
 * Set the Functions (with optional boosts) that will be included in the user's query to influence the score.
 * @param {String} functions - e.g.: `recip(rord(myfield),1,2,3)^1.5`
 *
 * @return {Query}
 * @api public
 */
Query.prototype.bf = function(functions){
   var self = this;
   var parameter = 'bf=' + functions;
   this.parameters.push(parameter);
   return self;
}
/**
 * Set the Functions (with optional boosts) that will be included in the user's query to influence the score.
 * @param {String} functions - e.g.: `recip(rord(myfield),1,2,3)^1.5`
 *
 * @return {Query}
 * @api public
 */
Query.prototype.boost = function(functions){
   var self = this;
   var parameter = 'boost=' + encodeURIComponent(functions);
   this.parameters.push(parameter);
   return self;
}
/**
 * Build a querystring with the array of `this.parameters`.
 *
 * @return {String}
 * @api private
 */
Query.prototype.build = function(){
   return this.parameters.join('&');
}
/**
 * Set the Query Highlighting parameter.
 *
 * @param {Object} options - set of options for Highlighting
 * @param {Boolean} [options.on=true] - Turn on or off Highlighting
 * @param {String|Array} [options.q] - This parameters specifies and overriding query for highlighting. Multiple values specified in an array will be chained together with AND.
 * @param {String} [options.qparser] - This parameter specifies the qparser for the hl.q query.
 * @param {String|Array} [options.fl] - 'Field list.' Fields to be highlighted. Multiple fields can be entered by providing an array.
 * @param {Number} [options.snippets] - This parameter defines the maximum number of snippets to generate per field. Any number of snippets from 0 to this number can be generated per field
 * @param {Number} [options.fragsize] - This parameter defines the size, in characters, of the fragments to consider for highlighting.
 * @param {Boolean} [options.mergeContiguous] - This parameter instructs Solr to collapse continguous fragments into a single fragment.
 * @param {Number} [options.maxAnalyzedChars] - This param specifies the number of characters into a document that Solr should look for suitable snippets.
 * @param {Number} [options.maxMultiValuedToExamine] - This param specifies the max number of entries in a multi-valued field to examine before stopping
 * @param {Number} [options.maxMultiValuedToMatch] - This param specifies the maximum number of matches in a multi-valued field that are found before stopping.
 * @param {String} [options.alternateField] - Specifies a field to be used as a backup default summary if Solr cannot generate a snippet.
 * @param {Number} [options.maxAlternateFieldLength] - Specifies the maximum number of characters of the field to return. A number <=0 means the field length is unlimited.
 * @param {String} [options.formatter] - Selects a formatter for the highlighted output. At the time of writing, the only legal value is 'simple'.
 * @param {String} [options.simplePre] - This parameter defines the string to place before the data to be highlighted.
 * @param {String} [options.simplePost] - This parameter defines the string to place after the data to be highlighted.
 * @param {String} [options.fragmenter] - Specifies a text snippet generator for highlighted text. Default is 'gap' but 'regex' is another option.
 * @param {Boolean} [options.highlightMultiTerm] - Turn on or off MultiTermHighlighting. If True, Solr will use Highlight phrase terms that appear in multiple fields.
 * @param {Boolean} [options.requireFieldMatch] - If set to True, this parameter will force Solr to highlight terms only if they appear in the specified field. If false, terms are highlighted in all requested fields regardless of which field matches the query.
 * @param {Boolean} [options.usePhraseHighlighter] - If set to True, Solr will use the Lucene SpanScorer class to highlight phrase terms only when they appear within the query phrase in the document.
 * @param {Number} [options.regexSlop] - When using the regex fragmenter, this number specifies the factor by which the fragmenter can stray from the ideal fragment size.
 * @param {String} [options.regexPattern] - This parameter specifies the regulat expression for fragmenting.
 * @param {Number} [options.regexMaxAnalyzedChars] - This parameters specifies the max number of characters to analyze from a field when using the regex fragmenter.
 * @param {Boolean} [options.preserveMulti] - If True, multi-valued fields will return all values in the order they were saved in the index. If False, only values that match the highlight request will be returned.
 * @param {Boolean} [options.payloads] - If usePhraseHighlighter is True, and the indexed field has payloads but not term vectors, the index payloads will be read into the highlighter's index along with the posting. If you don't want this behavior, you may set this parameter to False and save some memory.
 *
 * @return {Query}
 * @api public
 */
Query.prototype.hl = function(options){
   var self = this;
   if(options.on === false){
      this.parameters.push('hl=false');
   }else{
      this.parameters.push('hl=true');
   }
   if(options.q !== undefined){
      if ( typeof(options.q) === 'string' ){
         this.parameters.push('hl.q=' + encodeURIComponent(options.q));
      }else{
         this.parameters.push('hl.q=' + querystring.stringify(options.q, '%20AND%20',':'));
      }
   }
   if(options.qparser !== undefined){
      this.parameters.push('hl.qparser=' + encodeURIComponent(options.qparser));
   }
   if(options.fl !== undefined){
      if ( typeof(options.fl) === 'string' ){
         this.parameters.push('hl.fl=' + encodeURIComponent(options.fl));
      }else{
         this.parameters.push('hl.fl=' + options.fl.join(','));
      }
   }
   if(options.snippets !== undefined){
      this.parameters.push('hl.snippets=' + encodeURIComponent(options.snippets));
   }
   if(options.fragsize !== undefined){
      this.parameters.push('hl.fragsize=' + encodeURIComponent(options.fragsize));
   }
   if(options.mergeContiguous !== undefined){
      this.parameters.push('hl.mergeContiguous=' + encodeURIComponent(options.mergeContiguous));
   }
   if(options.requireFieldMatch !== undefined){
      this.parameters.push('hl.requireFieldMatch=' + encodeURIComponent(options.requireFieldMatch));
   }
   if(options.maxAnalyzedChars !== undefined){
      this.parameters.push('hl.maxAnalyzedChars=' + encodeURIComponent(options.maxAnalyzedChars));
   }
   if(options.maxMultiValuedToExamine !== undefined){
      this.parameters.push('hl.maxMultiValuedToExamine=' + encodeURIComponent(options.maxMultiValuedToExamine));
   }
   if(options.maxMultiValuedToMatch !== undefined){
      this.parameters.push('hl.maxMultiValuedToMatch=' + encodeURIComponent(options.maxMultiValuedToMatch));
   }
   if(options.alternateField){
      this.parameters.push('hl.alternateField=' + encodeURIComponent(options.alternateField));
   }
   if(options.maxAlternateFieldLength !== undefined){
      this.parameters.push('hl.maxAlternateFieldLength=' + encodeURIComponent(options.maxAlternateFieldLength));
   }
   if(options.formatter){
      this.parameters.push('hl.formatter=' + encodeURIComponent(options.formatter));
   }
   if(options.simplePre){
      this.parameters.push('hl.simple.pre=' + encodeURIComponent(options.simplePre));
   }else{
      this.parameters.push('hl.simple.pre=<em>');
   }
   if(options.simplePost){
      this.parameters.push('hl.simple.post=' + encodeURIComponent(options.simplePost));
   }else{
      this.parameters.push('hl.simple.post=<%2Fem>');
   }
   if(options.fragmenter){
      this.parameters.push('hl.fragmenter=' + encodeURIComponent(options.fragmenter));
   }
   if(options.highlightMultiTerm !== undefined){
      this.parameters.push('hl.highlightMultiTerm=' + encodeURIComponent(options.highlightMultiTerm));
   }
   if(options.usePhraseHighlighter !== undefined){
      this.parameters.push('hl.usePhraseHighlighter=' + encodeURIComponent(options.usePhraseHighlighter));
   }
   if(options.regexSlop !== undefined){
      this.parameters.push('hl.regex.slop=' + encodeURIComponent(options.regexSlop));
   }
   if(options.regexPattern){
      this.parameters.push('hl.regex.pattern=' + encodeURIComponent(options.regexPattern));
   }
   if(options.regexMaxAnalyzedChars){
      this.parameters.push('hl.regex.maxAnalyzedChars=' + encodeURIComponent(options.regexMaxAnalyzedChars));
   }
   if(options.preserveMulti !== undefined){
      this.parameters.push('hl.preserveMulti=' + encodeURIComponent(options.preserveMulti));
   }
   if(options.payloads !== undefined){
      this.parameters.push('hl.payloads=' + encodeURIComponent(options.payloads));
   }
   return self;
}

+ 957 - 0
src/doctor/node_modules/solr-client/lib/solr.js

@ -0,0 +1,957 @@
/*!
 * solr client
 * Copyright(c) 2011-2012 HipSnip Limited
 * Copyright(c) 2013-2014 Rémy Loubradou
 * Author Rémy Loubradou <remyloubradou@gmail.com>
 * MIT Licensed
 */
/**
 * Load dependencies
 */
var http = require('http'),
   https = require('https'),
   Query = require('./query'),
   Collection = require('./collection'),
   querystring = require('querystring'),
   format = require('./utils/format'),
   SolrError = require('./error/solr-error'),
   JSONStream = require('JSONStream'),
   duplexer = require('duplexer'),
   request = require('request'),
   JSONbig = require('json-bigint'),
   versionUtils = require('./utils/version');
/**
 * Expose `createClient()`.
 */
exports.createClient = createClient;
/**
 * Create an instance of `Client`
 *
 * @param {String|Object} [host='127.0.0.1'] - IP address or host address of the Solr server
 * @param {Number|String} [port='8983'] - port of the Solr server
 * @param {String} [core=''] - name of the Solr core requested
 * @param {String} [path='/solr'] - root path of all requests
 * @param {http.Agent} [agent] - HTTP Agent which is used for pooling sockets used in HTTP(s) client requests
 * @param {Boolean} [secure=false] - if true HTTPS will be used instead of HTTP
 * @param {Boolean} [bigint=false] - if true JSONbig serializer/deserializer will be used instead
 *                                    of JSON native serializer/deserializer
 * @param solrVersion ['3.2', '4.0', '5.0', '5.1'], check lib/utils/version.js for full reference
 *
 * @return {Client}
 * @api public
 */
function createClient(host, port, core, path, agent, secure, bigint, solrVersion){
  var options = (typeof host === 'object') ? host : {
      host : host,
      port : port,
      core : core,
      path : path,
      agent : agent,
      secure : secure,
      bigint : bigint,
      solrVersion: solrVersion
   };
  return new Client(options);
}
/**
 * Solr client
 * @constructor
 *
 * @param {Object} options - set of options used to request the Solr server
 * @param {String} options.host - IP address or host address of the Solr server
 * @param {Number|String} options.port - port of the Solr server
 * @param {String} options.core - name of the Solr core requested
 * @param {String} options.path - root path of all requests
 * @param {http.Agent} [options.agent] - HTTP Agent which is used for pooling sockets used in HTTP(s) client requests
 * @param {Boolean} [options.secure=false] - if true HTTPS will be used instead of HTTP
 * @param {Boolean} [options.bigint=false] - if true JSONbig serializer/deserializer will be used instead
 *                                    of JSON native serializer/deserializer
 *
 * @return {Client}
 * @api private
 */
function Client(options){
   this.options = {
      host : options.host || '127.0.0.1',
      port : options.port || '8983',
      core : options.core || '',
      path : options.path || '/solr',
      agent : options.agent,
      secure : options.secure || false,
      bigint : options.bigint || false,
      get_max_request_entity_size: options.get_max_request_entity_size || false,
      solrVersion: options.solrVersion || versionUtils.Solr3_2
   };
   // Default paths of all request handlers
   this.UPDATE_JSON_HANDLER = (versionUtils.version(this.options.solrVersion) >= versionUtils.Solr4_0) ? 'update' : 'update/json';
   this.UPDATE_HANDLER = 'update';
   this.SELECT_HANDLER = 'select';
   this.COLLECTIONS_HANDLER = 'admin/collections';
   this.ADMIN_PING_HANDLER = 'admin/ping';
   this.REAL_TIME_GET_HANDLER = 'get';
   this.SPELL_HANDLER = 'spell';
}
/**
 * Create credential using the basic access authentication method
 *
 * @param {String} username
 * @param {String} password
 *
 * @return {Client}
 * @api public
 */
Client.prototype.basicAuth = function(username,password){
   var self = this;
   this.options.authorization = 'Basic ' + new Buffer(username + ':' + password).toString('base64');
   return self;
}
/**
 * Remove authorization header
 *
 * @return {Client}
 * @api public
 */
Client.prototype.unauth = function(){
   var self = this;
   delete this.options.authorization;
   return self;
}
/**
 * Add a document or a list of documents
 *
 * @param {Object|Array} doc - document or list of documents to add into the Solr database
 * @param {Object} [options] -
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.add = function(docs,options,callback){
   if(typeof(options) === 'function'){
      callback = options;
      options = {};
   }
   docs = format.dateISOify(docs); // format `Date` object into string understable for Solr as a date.
   docs = Array.isArray(docs) ? docs : [docs];
   return this.update(docs,options,callback);
}
/**
 * Get a document by id or a list of documents by ids using the Real-time-get feature
 *  in SOLR4 (https://wiki.apache.org/solr/RealTimeGet)
 *
 * @param {String|Array} ids - id or list of ids that identify the documents to get
 * @param {Query|Object|String} [query] -
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.realTimeGet = function(ids, query, callback){
   if(typeof query === 'function'){
      callback = query;
      query = {};
   }
   ids = Array.isArray(ids) ? ids : [ids];
   query.ids = ids.join(',');
   return this.get(this.REAL_TIME_GET_HANDLER,query,callback);
}
/**
 * Add the remote resource located at the given path `options.path` into the Solr database.
 *
 * @param {Object} options -
 * @param {String} options.path - path of the file. HTTP URL, the full path or a path relative to the CWD of the running solr server must be used.
 * @param {String} [options.format='xml'] - format of the resource. XML, CSV or JSON formats must be used.
 * @param {String} [options.contentType='text/plain;charset=utf-8'] - content type of the resource
 * @param {Object} [options.parameters] - set of extras parameters pass along in the query.
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.addRemoteResource = function(options,callback){
   options.parameters = options.parameters || {};
   options.format = (options.format === 'xml' ? '' : options.format || ''); // reason: the default route of the XmlUpdateRequestHandle is /update and not /update/xml.
   options.parameters.commit = (options.parameters.commit === undefined ? false : options.parameters.commit);
   options.parameters['stream.contentType'] = options.contentType || 'text/plain;charset=utf-8';
   if(options.path.match(/^https?:\/\//)){
      options.parameters['stream.url'] = options.path;
   }else{
      options.parameters['stream.file'] = options.path;
   }
   var handler = this.UPDATE_HANDLER + '/' + options.format.toLowerCase();
   var query = querystring.stringify(options.parameters);
   return this.get(handler,query,callback);
}
/**
 * Create a writable/readable `Stream` to add documents into the Solr database
 *
 * @param {Object} [options] -
 *
 * return {Stream}
 * @api public
 */
Client.prototype.createAddStream = function(options){
   var path = [this.options.path,this.options.core, this.UPDATE_JSON_HANDLER + '?' + querystring.stringify(options) +'&wt=json']
      .filter(function(element){
         return element;
      })
      .join('/');
   var headers = {
      'content-type' : 'application/json',
      'charset' : 'utf-8'
   };
   if(this.options.authorization){
      headers['authorization'] = this.options.authorization;
   }
   var protocol = this.options.secure ? 'https' : 'http';
   var optionsRequest = {
      url : protocol + '://' + this.options.host +':' + this.options.port + path ,
      method : 'POST',
      headers : headers
   };
   var jsonStreamStringify = JSONStream.stringify();
   var postRequest = request(optionsRequest);
   jsonStreamStringify.pipe(postRequest);
   var duplex = duplexer(jsonStreamStringify,postRequest);
   return duplex ;
}
/**
 * Commit last added and removed documents, that means your documents are now indexed.
 *
 * @param {Object} options
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.commit = function(options,callback){
   if(typeof(options) === 'function'){
      callback = options;
      options = {};
   }
   var data = {
      commit : options || {}
   };
   return this.update(data,callback);
}
/**
 * Call Lucene's IndexWriter.prepareCommit, the changes won't be visible in the index.
 *
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.prepareCommit = function(callback){
   return this.update({},{ prepareCommit : true},callback);
}
/**
 * Soft commit all changes
 *
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.softCommit = function(callback){
   return this.update({},{ softCommit : true},callback);
}
/**
 * Delete documents based on the given `field` and `text`.
 *
 * @param {String} field
 * @param {String} text
 * @param {Object} [options]
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.delete = function(field,text,options,callback) {
   if(typeof(options) === 'function'){
      callback = options;
      options = {};
   }
   text = format.dateISOify(text);
   var data = {
      'delete' :  {
         query : field +  ':'  + format.escapeSpecialChars(text)
      }
   };
   return this.update(data,options,callback);
}
/**
 * Delete a range of documents based on the given `field`, `start` and `stop` arguments.
 *
 * @param {String} field
 * @param {String|Date} start
 * @param {String|Date} stop
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.deleteByRange = function(field,start,stop,options,callback){
   if(typeof(options) === 'function'){
      callback = options;
      options = {};
   }
   start = format.dateISOify(start);
   stop = format.dateISOify(stop);
   var query = field + ':[' + start + ' TO ' + stop + ']';
   return this.deleteByQuery(query,options,callback);
}
/**
 * Delete the document with the given `id`
 *
 * @param {String|Number} id - id of the document you want to delete
 * @param {Object} [options] -
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.deleteByID = function(id,options,callback){
   if(typeof(options) === 'function'){
      callback = options;
      options = {};
   }
   var data = {
      'delete' : {
         id : id
      }
   };
   return this.update(data,options,callback);
}
/**
 * Delete documents matching the given `query`
 *
 * @param {String} query -
 * @param {Object} [options] -
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.deleteByQuery = function(query,options,callback){
   if(typeof(options) === 'function'){
      callback = options;
      options = {};
   }
   var data = {
      'delete' : {
         query : query
      }
   };
   return this.update(data,options,callback);
}
/**
 * Delete all documents
 *
 * @param {Object} [options] -
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.deleteAll = function(options,callback){
   return this.deleteByQuery('*:*',options,callback);
}
/**
 * Optimize the index
 *
 * @param {Object} options -
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.optimize = function(options,callback){
   if(typeof(options) === 'function'){
      callback = options;
      options = {};
   }
   var data = {
      optimize : options || {}
   };
   return this.update(data,callback);
}
/**
 * Rollback all add/delete commands made since the last commit.
 *
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.rollback = function(callback){
   var data = {
      rollback : {}
   };
   return this.update(data,callback);
}
/**
 * Send an update command to the Solr server with the given `data` stringified in the body.
 *
 * @param {Object} data - data sent to the Solr server
 * @param {Object} [options] -
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api private
 */
Client.prototype.update = function(data,options,callback){
   if(typeof(options) === 'function'){
      callback = options;
      options = {};
   }
   var json = pickJSON(this.options.bigint).stringify(data);
   var fullPath = [this.options.path,this.options.core, this.UPDATE_JSON_HANDLER + '?' + querystring.stringify(options) +'&wt=json']
                              .filter(function(element){
                                 return element;
                              })
                              .join('/');
   var params = {
      host : this.options.host,
      port : this.options.port,
      fullPath : fullPath,
      json : json,
      secure : this.options.secure,
      bigint : this.options.bigint,
      authorization : this.options.authorization,
      agent : this.options.agent
   };
   return postJSON(params,callback);
}
/**
 * Search documents matching the `query`
 *
 * @param {Query|Object|String} query
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.search = function(query,callback){
   return this.get(this.SELECT_HANDLER, query, callback);
}
/**
 * Execute an Admin Collections task on `collection`
 *
 * @param {Query|Object|String} collection
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.executeCollection = function(collection,callback){
   return this.get(this.COLLECTIONS_HANDLER, collection, callback);
}
/**
 * Search for all documents
 *
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.searchAll = function(callback){
   return this.search('q=*', callback);
}
/**
 * Search documents matching the `query`
 *
 * Spellcheck is also enabled.
 *
 * @param {Query|Object|String} query
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.spell = function(query,callback){
   return this.get(this.SPELL_HANDLER, query, callback);
}
/**
 * Send an arbitrary HTTP GET request to Solr on the specified `handler` (as Solr like to call it i.e path)
 *
 * @param {String} handler
 * @param {Query|Object|String} [query]
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.get = function(handler,query,callback){
   var parameters = '';
   if(typeof query === 'function'){
      callback = query;
   }else if((query instanceof Query) || (query instanceof Collection)){
      parameters += query.build();
   }else if(typeof query === 'object'){
      parameters += querystring.stringify(query);
   }else if(typeof query === 'string'){
      parameters += query;
   }
   var pathArray;
   if(handler != 'admin/collections'){
      pathArray = [this.options.path,this.options.core,handler + '?' + parameters + '&wt=json'];
   } else {
      pathArray = [this.options.path,handler + '?' + parameters + '&wt=json'];
   }
   var fullPath = pathArray.filter(function(element){
                 return element;
              }).join('/');
   var approxUrlLength = 10 + Buffer.byteLength(this.options.host) + (this.options.port+"").length + Buffer.byteLength(fullPath); // Buffer (10) accounts for protocol and special characters like ://, port colon, and initial slash etc
   if (this.options.get_max_request_entity_size === false || approxUrlLength <= this.options.get_max_request_entity_size) {
      var params = {
         host: this.options.host,
         port: this.options.port,
         fullPath: fullPath,
         secure: this.options.secure,
         bigint: this.options.bigint,
         authorization: this.options.authorization,
         agent: this.options.agent
      };
      return getJSON(params, callback);
   } else {
      // Funnel this through a POST because it's too large
      return this.post(handler, query, callback);
   }
}
/**
 * Send an arbitrary HTTP POST request to Solr on the specified `handler` (as Solr like to call it i.e path)
 *
 * @param {String} handler
 * @param {Query|Object|String} [query]
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.post = function(handler,query,callback){
   var parameters = '';
   if(typeof query === 'function'){
      callback = query;
   }else if(query instanceof Query){
      parameters += query.build();
   }else if(typeof query === 'object'){
      parameters += querystring.stringify(query);
   }else if(typeof query === 'string'){
      parameters += query;
   }
   var pathArray;
   if(handler != 'admin/collections'){
      pathArray = [this.options.path,this.options.core,handler + '?' + parameters + '&wt=json'];
   } else {
      pathArray = [this.options.path,handler + '?' + parameters + '&wt=json'];
   }
   var fullPath = pathArray.filter(function(element){
                 return element;
              }).join('/');
   var params = {
      host : this.options.host,
      port : this.options.port,
      fullPath : fullPath,
      params : parameters,
      secure : this.options.secure,
      bigint : this.options.bigint,
      authorization : this.options.authorization,
      agent : this.options.agent
   };
   return postForm(params,callback);
}
/**
 * Create an instance of `Query`
 *
 * @return {Query}
 * @api public
 */
Client.prototype.query = function(){
   return new Query(this.options);
}
/**
 * Create an instance of `Query`
 * NOTE: This method will be deprecated in the v0.6 release. Please use `Client.query()` instead.
 *
 * @return {Query}
 * @api public
 */
Client.prototype.createQuery = function(){
   return new Query(this.options);
}
/**
 * Create an instance of `Collection`
 *
 * @return {Collection}
 * @api public
 */
Client.prototype.collection = function(){
   return new Collection();
}
/**
 * Expose `format.escapeSpecialChars` from `Client.escapeSpecialChars`
 */
Client.prototype.escapeSpecialChars = format.escapeSpecialChars;
/**
 * Ping the Solr server
 *
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api public
 */
Client.prototype.ping = function(callback){
   return this.get(this.ADMIN_PING_HANDLER, callback);
}
/**
 * Pick appropriate protocol based on the given `secure` flag
 *
 * @param {Boolean} secure -
 * @return {Object} http or https module
 * @api private
 */
function pickProtocol(secure){
   return secure ? https : http;
};
/**
 * Pick appropriate JSON serializer/deserializer library based on the given `bigint` flag
 * @param {Boolean} bigint - whenever to handle big number correctly or not (the reason for not using JSONbig all the times is it has an important performance cost)
 * @return {Object} JSON or JSONbig serializer/deserializer
 * @api private
 */
function pickJSON(bigint){
   return bigint ? JSONbig : JSON;
};
/**
 * HTTP POST request. Send update commands to the Solr server (commit, add, delete, optimize)
 *
 * @param {Object} params
 * @param {String} params.host - IP address or host address of the Solr server
 * @param {Number|String} params.port - port of the Solr server
 * @param {String} params.core - name of the Solr core requested
 * @param {String} params.authorization - value of the authorization header
 * @param {String} params.fullPath - full path of the request
 * @param {String} params.json -
 * @param {Boolean} params.secure -
 * @param {Boolean} params.bigint -
 * @param {http.Agent} [params.agent] -
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api private
 */
function postJSON(params,callback){
   var headers = {
      'content-type' : 'application/json; charset=utf-8',
      'content-length':  Buffer.byteLength(params.json),
      'accept' : 'application/json; charset=utf-8'
   };
   if(params.authorization){
      headers['authorization'] = params.authorization;
   }
   var options = {
      host : params.host,
      port : params.port,
      method : 'POST',
      headers : headers,
      path : params.fullPath
   };
   if(params.agent !== undefined){
      options.agent = params.agent;
   }
   var request = pickProtocol(params.secure).request(options);
   request.on('response', handleJSONResponse(request, params.bigint, callback));
   request.on('error',function onError(err){
      if (callback) callback(err,null);
   });
   request.write(params.json);
   request.end();
   return request;
};
/**
 * HTTP POST request. Send update commands to the Solr server using form encoding (e.g. search)
 *
 * @param {Object} params
 * @param {String} params.host - IP address or host address of the Solr server
 * @param {Number|String} params.port - port of the Solr server
 * @param {String} params.core - name of the Solr core requested
 * @param {String} params.authorization - value of the authorization header
 * @param {String} params.fullPath - full path of the request
 * @param {String} params.params - form params
 * @param {Boolean} params.secure -
 * @param {Boolean} params.bigint -
 * @param {http.Agent} [params.agent] -
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api private
 */
function postForm(params,callback){
   var headers = {
      'content-type' : 'application/x-www-form-urlencoded; charset=utf-8',
      'content-length':  Buffer.byteLength(params.params),
      'accept' : 'application/json; charset=utf-8'
   };
   if(params.authorization){
      headers['authorization'] = params.authorization;
   }
   var options = {
      host : params.host,
      port : params.port,
      method : 'POST',
      headers : headers,
      path : params.fullPath
   };
   if(params.agent !== undefined){
      options.agent = params.agent;
   }
   var request = pickProtocol(params.secure).request(options);
   request.on('response', handleJSONResponse(request, params.bigint, callback));
   request.on('error',function onError(err){
      if (callback) callback(err,null);
   });
   request.write(params.params);
   request.end();
   return request;
};
/**
 * HTTP GET request.  Send a query command to the Solr server (query)
 *
 * @param {Object} params
 * @param {String} params.host - IP address or host address of the Solr server
 * @param {Number|String} params.port - port of the Solr server
 * @param {String} params.core - name of the Solr core requested
 * @param {String} params.authorization - value of the authorization header
 * @param {String} params.fullPath - full path of the request, contains query parameters
 * @param {Boolean} params.secure -
 * @param {Boolean} params.bigint -
 * @param {http.Agent} [params.agent] -
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @return {http.ClientRequest}
 * @api private
 */
function getJSON(params,callback){
   var headers = {
      'accept' : 'application/json; charset=utf-8'
   };
   var options = {
      host : params.host,
      port : params.port,
      path : params.fullPath,
      headers : headers
   };
   if(params.agent !== undefined){
      options.agent = params.agent;
   }
    if(params.authorization){
      var headers = {
         'authorization' : params.authorization
      };
      options.headers = headers;
   }
   var request = pickProtocol(params.secure).get(options);
   request.on('response', handleJSONResponse(request, params.bigint, callback));
   request.on('error',function(err){
      if (callback) callback(err,null);
   });
   return request;
};
/**
 * Handle HTTP JSON response from Solr
 *
 * @param {Function} callback(err,obj) - a function executed when the Solr server responds or an error occurs
 * @param {Error} callback().err
 * @param {Object} callback().obj - JSON response sent by the Solr server deserialized
 *
 * @api private
 */
function handleJSONResponse(request, bigint, callback){
   return function onJSONResponse(response){
      var text = '';
      var err = null;
      var data = null;
      // This properly handles multi-byte characters
      response.setEncoding('utf-8');
      response.on('data',function(chunk){
         text += chunk;
      });
      response.on('end',function(){
         if(response.statusCode < 200 || response.statusCode > 299){
            err = new SolrError(request,response,text);
            if(callback)  callback(err,null);
         }else{
            try{
               data = pickJSON(bigint).parse(text);
            }catch(error){
               err = error;
            }finally{
               if(callback)  callback(err,data);
            }
         }
      });
   };
};

+ 15 - 0
src/doctor/node_modules/solr-client/lib/utils/array.js

@ -0,0 +1,15 @@
/**
 *
 * @param value
 * @param defaultIfNull - Will set the value to the default if it is Null or Undefined
 * @returns {*[]}
 */
exports.toArray = function toArray(value, defaultIfNull) {
  defaultIfNull = defaultIfNull || '';
  function defaultValue(value) {
    return (value === null || value === undefined) ? defaultIfNull : value;
  }
  return (Array.isArray(value)) ? value : [defaultValue(value)];
};

+ 136 - 0
src/doctor/node_modules/solr-client/lib/utils/format.js

@ -0,0 +1,136 @@
/**
 * Expose `dateISOify()` and `toISOString()`
 */
exports.dateISOify = dateISOify;
exports.toISOString = toISOString;
/**
 * ISOify `Date` objects (possibly in collections)
 *
 * @param {Array|Object} obj
 *
 * @return {Array|Object}
 * @api private
 */
function dateISOify(obj){
   if( obj instanceof Array ){
      for(var i = 0; i < obj.length; i++){
         obj[i] = dateISOify(obj[i]);
      }
   }else if(obj instanceof Object && !(obj instanceof Date) ){
      for(var key in obj){
         if( obj[key] instanceof Date ) obj[key] = toISOString(obj[key]);
      }
   }else{
      if( obj instanceof Date ) obj = toISOString(obj);
   }
   return obj;
};
/**
 * ISOify a single `Date` object
 * Sidesteps `Invalid Date` objects by returning `null` instead
 *
 * @param {Date}
 *
 * @return {null|String}
 * @api private
 */
function toISOString(date) {
   return (date && !isNaN(date.getTime())) ? date.toISOString() : null;
};
/**
 * Expose `stringify()`
 */
exports.stringify = stringify;
/**
 * Serialize an object to a string. Optionally override the default separator ('&') and assignment ('=') characters.
 *
 * @param {Object} obj - object to serialiaze
 * @param {String} [sep] - separator character
 * @param {String} [eq] - assignment character
 * @param {String} [name] -
 *
 * @return {String}
 * @api private
 */
function stringify(obj, sep, eq, name) {
  sep = sep || '&';
  eq = eq || '=';
  obj = (obj === null) ? undefined : obj;
  switch (typeof obj) {
    case 'object':
      return Object.keys(obj).map(function(k) {
        if (Array.isArray(obj[k])) {
          return obj[k].map(function(v) {
            return stringifyPrimitive(k) +
                   eq +
                   stringifyPrimitive(v);
          }).join(sep);
        } else {
          return stringifyPrimitive(k) +
                 eq +
                 stringifyPrimitive(obj[k]);
        }
      }).join(sep);
    default:
      if (!name) return '';
      return stringifyPrimitive(name) + eq +
             stringifyPrimitive(obj);
  }
};
/**
 * Stringify a primitive
 *
 * @param {String|Boolean|Number} v - primitive value
 *
 * @return {String}
 * @api private
 */
function stringifyPrimitive(v) {
  switch (typeof v) {
    case 'string':
      return v;
    case 'boolean':
      return v ? 'true' : 'false';
    case 'number':
      return isFinite(v) ? v : '';
    default:
      return '';
  }
};
/**
 * Expose `escapeSpecialChars`
 */
 exports.escapeSpecialChars = escapeSpecialChars;
 /**
  * Escape special characters that are part of the query syntax of Lucene
  *
  * @param {String} s - string to escape
  *
  * @return {String}
  * @api public
  */
function escapeSpecialChars(s){
  return s.replace(/([\+\-!\(\)\{\}\[\]\^"~\*\?:\\])/g, function(match) {
    return '\\' + match;
  })
  .replace(/&&/g, '\\&\\&')
  .replace(/\|\|/g, '\\|\\|');
}

+ 21 - 0
src/doctor/node_modules/solr-client/lib/utils/type.js

@ -0,0 +1,21 @@
/**
 *
 * @param value - The value to check against
 * @param strict - Pass true if you want to make sure the number is fully and only composed of digits, false to just check if we can extract a number via parseInt(). Default to true.
 * @returns boolean
 */
exports.isNumber = function isNumber(value, strict) {
  strict = (strict === undefined ? true : strict);
  var digitRegex = /^\-?\d+$/; // At least 1 digit, possibly a minus sign before
  if (typeof value === 'number') {
    return true;
  } else {
    // String ?
    if (strict) {
      return (('' + value).match(digitRegex) !== null);
    } else {
      return !isNaN(parseInt(value));
    }
  }
};

+ 37 - 0
src/doctor/node_modules/solr-client/lib/utils/version.js

@ -0,0 +1,37 @@
/**
 * The purpose of those helpers is to centralize and standardize the work on detecting current running Solr Version
 */
var Solr3_2 = 302;
var Solr4_0 = 400;
var Solr5_0 = 500;
var Solr5_1 = 501;
/**
 * Enum that lists supported versions of Solr. Pass one of the keys from this enum as a solrVersion property
 *
 * @type {{3.2: number, 4.0: number, 5.0: number, 5.1: number}}
 */
var versionsEnum = {
  '3.2': Solr3_2,
  '4.0': Solr4_0,
  '5.0': Solr5_0,
  '5.1': Solr5_1
};
exports.versionsEnum = versionsEnum;
exports.Solr3_2 = Solr3_2;
exports.Solr4_0 = Solr4_0;
exports.Solr5_0 = Solr5_0;
exports.Solr5_1 = Solr5_1;
/**
 * solrVersion must match one of enum keys
 * If a number is passed, it'll be assume a .0 release (3 -> 3.0)
 * If nothing matches, it will be assumed 3.2
 *
 * @param solrVersion
 */
exports.version = function(solrVersion) {
  return (typeof solrVersion === "number") ? (versionsEnum[''+solrVersion+'.0']) : (versionsEnum[solrVersion] ? versionsEnum[solrVersion] : versionsEnum['3.2']);
};

+ 10 - 0
src/doctor/node_modules/solr-client/main.js

@ -0,0 +1,10 @@
/*!
 * solr client
 * Copyright(c) 2011-2012 HipSnip Limited
 * Author Rémy Loubradou <remyloubradou@gmail.com>
 * MIT Licensed
 */
module.exports = exports = require("./lib/solr");

+ 15 - 0
src/doctor/node_modules/solr-client/node_modules/.bin/JSONStream

@ -0,0 +1,15 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
    *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
  "$basedir/node"  "$basedir/../JSONStream/index.js" "$@"
  ret=$?
else 
  node  "$basedir/../JSONStream/index.js" "$@"
  ret=$?
fi
exit $ret

+ 7 - 0
src/doctor/node_modules/solr-client/node_modules/.bin/JSONStream.cmd

@ -0,0 +1,7 @@
@IF EXIST "%~dp0\node.exe" (
  "%~dp0\node.exe"  "%~dp0\..\JSONStream\index.js" %*
) ELSE (
  @SETLOCAL
  @SET PATHEXT=%PATHEXT:;.JS;=;%
  node  "%~dp0\..\JSONStream\index.js" %*
)

+ 2 - 0
src/doctor/node_modules/solr-client/node_modules/JSONStream/.npmignore

@ -0,0 +1,2 @@
node_modules/*
node_modules

+ 3 - 0
src/doctor/node_modules/solr-client/node_modules/JSONStream/.travis.yml

@ -0,0 +1,3 @@
language: node_js
node_js:
  - "0.10"

+ 15 - 0
src/doctor/node_modules/solr-client/node_modules/JSONStream/LICENSE.APACHE2

@ -0,0 +1,15 @@
Apache License, Version 2.0
Copyright (c) 2011 Dominic Tarr
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

+ 24 - 0
src/doctor/node_modules/solr-client/node_modules/JSONStream/LICENSE.MIT

@ -0,0 +1,24 @@
The MIT License
Copyright (c) 2011 Dominic Tarr
Permission is hereby granted, free of charge, 
to any person obtaining a copy of this software and 
associated documentation files (the "Software"), to 
deal in the Software without restriction, including 
without limitation the rights to use, copy, modify, 
merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom 
the Software is furnished to do so, 
subject to the following conditions:
The above copyright notice and this permission notice 
shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 13 - 0
src/doctor/node_modules/solr-client/node_modules/JSONStream/examples/all_docs.js

@ -0,0 +1,13 @@
var request = require('request')
  , JSONStream = require('JSONStream')
  , es = require('event-stream')
var parser = JSONStream.parse(['rows', true]) //emit parts that match this path (any element of the rows array)
  , req = request({url: 'http://isaacs.couchone.com/registry/_all_docs'})
  , logger = es.mapSync(function (data) {  //create a stream that logs to stderr,
    console.error(data)
    return data  
  })
req.pipe(parser)
parser.pipe(logger)

+ 203 - 0
src/doctor/node_modules/solr-client/node_modules/JSONStream/index.js

@ -0,0 +1,203 @@
#! /usr/bin/env node
'use strict'
var Parser = require('jsonparse')
  , through = require('through')
/*
  the value of this.stack that creationix's jsonparse has is weird.
  it makes this code ugly, but his problem is way harder that mine,
  so i'll forgive him.
*/
exports.parse = function (path, map) {
  var parser = new Parser()
  var stream = through(function (chunk) {
    if('string' === typeof chunk)
      chunk = new Buffer(chunk)
    parser.write(chunk)
  },
  function (data) {
    if(data)
      stream.write(data)
    stream.queue(null)
  })
  if('string' === typeof path)
    path = path.split('.').map(function (e) {
      if (e === '*')
        return true
      else if (e === '') // '..'.split('.') returns an empty string
        return {recurse: true}
      else
        return e
    })
  var count = 0, _key
  if(!path || !path.length)
    path = null
  parser.onValue = function (value) {
    if (!this.root)
      stream.root = value
    if(! path) return
    var i = 0 // iterates on path
    var j  = 0 // iterates on stack
    while (i < path.length) {
      var key = path[i]
      var c
      j++
      if (key && !key.recurse) {
        c = (j === this.stack.length) ? this : this.stack[j]
        if (!c) return
        if (! check(key, c.key)) return
        i++
      } else {
        i++
        var nextKey = path[i]
        if (! nextKey) return
        while (true) {
          c = (j === this.stack.length) ? this : this.stack[j]
          if (!c) return
          if (check(nextKey, c.key)) {
            i++;
            this.stack[j].value = null
            break
          }
          j++
        }
      }
    }
    if (j !== this.stack.length) return
    count ++
    var actualPath = this.stack.slice(1).map(function(element) { return element.key }).concat([this.key])
    var data = this.value[this.key]
    if(null != data)
      if(null != (data = map ? map(data, actualPath) : data))
        stream.queue(data)
    delete this.value[this.key]
    for(var k in this.stack)
      this.stack[k].value = null
  }
  parser._onToken = parser.onToken;
  parser.onToken = function (token, value) {
    parser._onToken(token, value);
    if (this.stack.length === 0) {
      if (stream.root) {
        if(!path)
          stream.queue(stream.root)
        count = 0;
        stream.root = null;
      }
    }
  }
  
  parser.onError = function (err) {
    if(err.message.indexOf("at position") > -1)
      err.message = "Invalid JSON (" + err.message + ")";
    stream.emit('error', err)
  }
  return stream
}
function check (x, y) {
  if ('string' === typeof x)
    return y == x
  else if (x && 'function' === typeof x.exec)
    return x.exec(y)
  else if ('boolean' === typeof x)
    return x
  else if ('function' === typeof x)
    return x(y)
  return false
}
exports.stringify = function (op, sep, cl, indent) {
  indent = indent || 0
  if (op === false){
    op = ''
    sep = '\n'
    cl = ''
  } else if (op == null) {
    op = '[\n'
    sep = '\n,\n'
    cl = '\n]\n'
  }
  //else, what ever you like
  var stream
    , first = true
    , anyData = false
  stream = through(function (data) {
    anyData = true
    var json = JSON.stringify(data, null, indent)
    if(first) { first = false ; stream.queue(op + json)}
    else stream.queue(sep + json)
  },
  function (data) {
    if(!anyData)
      stream.queue(op)
    stream.queue(cl)
    stream.queue(null)
  })
  return stream
}
exports.stringifyObject = function (op, sep, cl, indent) {
  indent = indent || 0
  if (op === false){
    op = ''
    sep = '\n'
    cl = ''
  } else if (op == null) {
    op = '{\n'
    sep = '\n,\n'
    cl = '\n}\n'
  }
  //else, what ever you like
  var first = true
  var anyData = false
  var stream = through(function (data) {
    anyData = true
    var json = JSON.stringify(data[0]) + ':' + JSON.stringify(data[1], null, indent)
    if(first) { first = false ; this.queue(op + json)}
    else this.queue(sep + json)
  },
  function (data) {
    if(!anyData) this.queue(op)
    this.queue(cl)
    this.queue(null)
  })
  return stream
}
if(!module.parent && process.title !== 'browser') {
  process.stdin
    .pipe(exports.parse(process.argv[2]))
    .pipe(exports.stringify('[', ',\n', ']\n', 2))
    .pipe(process.stdout)
}

+ 1 - 0
src/doctor/node_modules/solr-client/node_modules/JSONStream/node_modules/jsonparse/.npmignore

@ -0,0 +1 @@
node_modules

+ 24 - 0
src/doctor/node_modules/solr-client/node_modules/JSONStream/node_modules/jsonparse/LICENSE

@ -0,0 +1,24 @@
The MIT License
Copyright (c) 2012 Tim Caswell
Permission is hereby granted, free of charge, 
to any person obtaining a copy of this software and 
associated documentation files (the "Software"), to 
deal in the Software without restriction, including 
without limitation the rights to use, copy, modify, 
merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom 
the Software is furnished to do so, 
subject to the following conditions:
The above copyright notice and this permission notice 
shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 11 - 0
src/doctor/node_modules/solr-client/node_modules/JSONStream/node_modules/jsonparse/README.markdown

@ -0,0 +1,11 @@
This is a streaming JSON parser.  For a simpler, sax-based version see this gist: https://gist.github.com/1821394
The MIT License (MIT)
Copyright (c) 2011-2012 Tim Caswell
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 0 - 0
src/doctor/node_modules/solr-client/node_modules/JSONStream/node_modules/jsonparse/bench.js


Some files were not shown because too many files changed in this diff