index.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const lodash_1 = require("../utils/lodash");
  4. const util_1 = require("util");
  5. const events_1 = require("events");
  6. const Deque = require("denque");
  7. const command_1 = require("../command");
  8. const commander_1 = require("../commander");
  9. const utils_1 = require("../utils");
  10. const standard_as_callback_1 = require("standard-as-callback");
  11. const eventHandler = require("./event_handler");
  12. const connectors_1 = require("../connectors");
  13. const ScanStream_1 = require("../ScanStream");
  14. const commands = require("redis-commands");
  15. const PromiseContainer = require("../promiseContainer");
  16. const transaction_1 = require("../transaction");
  17. const RedisOptions_1 = require("./RedisOptions");
  18. var debug = utils_1.Debug("redis");
  19. /**
  20. * Creates a Redis instance
  21. *
  22. * @constructor
  23. * @param {(number|string|Object)} [port=6379] - Port of the Redis server,
  24. * or a URL string(see the examples below),
  25. * or the `options` object(see the third argument).
  26. * @param {string|Object} [host=localhost] - Host of the Redis server,
  27. * when the first argument is a URL string,
  28. * this argument is an object represents the options.
  29. * @param {Object} [options] - Other options.
  30. * @param {number} [options.port=6379] - Port of the Redis server.
  31. * @param {string} [options.host=localhost] - Host of the Redis server.
  32. * @param {string} [options.family=4] - Version of IP stack. Defaults to 4.
  33. * @param {string} [options.path=null] - Local domain socket path. If set the `port`,
  34. * `host` and `family` will be ignored.
  35. * @param {number} [options.keepAlive=0] - TCP KeepAlive on the socket with a X ms delay before start.
  36. * Set to a non-number value to disable keepAlive.
  37. * @param {boolean} [options.noDelay=true] - Whether to disable the Nagle's Algorithm. By default we disable
  38. * it to reduce the latency.
  39. * @param {string} [options.connectionName=null] - Connection name.
  40. * @param {number} [options.db=0] - Database index to use.
  41. * @param {string} [options.password=null] - If set, client will send AUTH command
  42. * with the value of this option when connected.
  43. * @param {boolean} [options.dropBufferSupport=false] - Drop the buffer support for better performance.
  44. * This option is recommended to be enabled when
  45. * handling large array response and you don't need the buffer support.
  46. * @param {boolean} [options.enableReadyCheck=true] - When a connection is established to
  47. * the Redis server, the server might still be loading the database from disk.
  48. * While loading, the server not respond to any commands.
  49. * To work around this, when this option is `true`,
  50. * ioredis will check the status of the Redis server,
  51. * and when the Redis server is able to process commands,
  52. * a `ready` event will be emitted.
  53. * @param {boolean} [options.enableOfflineQueue=true] - By default,
  54. * if there is no active connection to the Redis server,
  55. * commands are added to a queue and are executed once the connection is "ready"
  56. * (when `enableReadyCheck` is `true`,
  57. * "ready" means the Redis server has loaded the database from disk, otherwise means the connection
  58. * to the Redis server has been established). If this option is false,
  59. * when execute the command when the connection isn't ready, an error will be returned.
  60. * @param {number} [options.connectTimeout=10000] - The milliseconds before a timeout occurs during the initial
  61. * connection to the Redis server.
  62. * @param {boolean} [options.autoResubscribe=true] - After reconnected, if the previous connection was in the
  63. * subscriber mode, client will auto re-subscribe these channels.
  64. * @param {boolean} [options.autoResendUnfulfilledCommands=true] - If true, client will resend unfulfilled
  65. * commands(e.g. block commands) in the previous connection when reconnected.
  66. * @param {boolean} [options.lazyConnect=false] - By default,
  67. * When a new `Redis` instance is created, it will connect to Redis server automatically.
  68. * If you want to keep the instance disconnected until a command is called, you can pass the `lazyConnect` option to
  69. * the constructor:
  70. *
  71. * ```javascript
  72. * var redis = new Redis({ lazyConnect: true });
  73. * // No attempting to connect to the Redis server here.
  74. * // Now let's connect to the Redis server
  75. * redis.get('foo', function () {
  76. * });
  77. * ```
  78. * @param {Object} [options.tls] - TLS connection support. See https://github.com/luin/ioredis#tls-options
  79. * @param {string} [options.keyPrefix=''] - The prefix to prepend to all keys in a command.
  80. * @param {function} [options.retryStrategy] - See "Quick Start" section
  81. * @param {number} [options.maxRetriesPerRequest] - See "Quick Start" section
  82. * @param {number} [options.maxLoadingRetryTime=10000] - when redis server is not ready, we will wait for
  83. * `loading_eta_seconds` from `info` command or maxLoadingRetryTime (milliseconds), whichever is smaller.
  84. * @param {function} [options.reconnectOnError] - See "Quick Start" section
  85. * @param {boolean} [options.readOnly=false] - Enable READONLY mode for the connection.
  86. * Only available for cluster mode.
  87. * @param {boolean} [options.stringNumbers=false] - Force numbers to be always returned as JavaScript
  88. * strings. This option is necessary when dealing with big numbers (exceed the [-2^53, +2^53] range).
  89. * @param {boolean} [options.enableTLSForSentinelMode=false] - Whether to support the `tls` option
  90. * when connecting to Redis via sentinel mode.
  91. * @param {NatMap} [options.natMap=null] NAT map for sentinel connector.
  92. * @param {boolean} [options.updateSentinels=true] - Update the given `sentinels` list with new IP
  93. * addresses when communicating with existing sentinels.
  94. * @extends [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter)
  95. * @extends Commander
  96. * @example
  97. * ```js
  98. * var Redis = require('ioredis');
  99. *
  100. * var redis = new Redis();
  101. *
  102. * var redisOnPort6380 = new Redis(6380);
  103. * var anotherRedis = new Redis(6380, '192.168.100.1');
  104. * var unixSocketRedis = new Redis({ path: '/tmp/echo.sock' });
  105. * var unixSocketRedis2 = new Redis('/tmp/echo.sock');
  106. * var urlRedis = new Redis('redis://user:password@redis-service.com:6379/');
  107. * var urlRedis2 = new Redis('//localhost:6379');
  108. * var urlRedisTls = new Redis('rediss://user:password@redis-service.com:6379/');
  109. * var authedRedis = new Redis(6380, '192.168.100.1', { password: 'password' });
  110. * ```
  111. */
  112. exports.default = Redis;
  113. function Redis() {
  114. if (!(this instanceof Redis)) {
  115. console.error(new Error("Calling `Redis()` like a function is deprecated. Using `new Redis()` instead.").stack.replace("Error", "Warning"));
  116. return new Redis(arguments[0], arguments[1], arguments[2]);
  117. }
  118. this.parseOptions(arguments[0], arguments[1], arguments[2]);
  119. events_1.EventEmitter.call(this);
  120. commander_1.default.call(this);
  121. this.resetCommandQueue();
  122. this.resetOfflineQueue();
  123. this.connectionEpoch = 0;
  124. if (this.options.Connector) {
  125. this.connector = new this.options.Connector(this.options);
  126. }
  127. else if (this.options.sentinels) {
  128. this.connector = new connectors_1.SentinelConnector(this.options);
  129. }
  130. else {
  131. this.connector = new connectors_1.StandaloneConnector(this.options);
  132. }
  133. this.retryAttempts = 0;
  134. // end(or wait) -> connecting -> connect -> ready -> end
  135. if (this.options.lazyConnect) {
  136. this.setStatus("wait");
  137. }
  138. else {
  139. this.connect().catch(lodash_1.noop);
  140. }
  141. }
  142. util_1.inherits(Redis, events_1.EventEmitter);
  143. Object.assign(Redis.prototype, commander_1.default.prototype);
  144. /**
  145. * Create a Redis instance
  146. *
  147. * @deprecated
  148. */
  149. Redis.createClient = function (...args) {
  150. // @ts-ignore
  151. return new Redis(...args);
  152. };
  153. /**
  154. * Default options
  155. *
  156. * @var defaultOptions
  157. * @private
  158. */
  159. Redis.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
  160. Redis.prototype.resetCommandQueue = function () {
  161. this.commandQueue = new Deque();
  162. };
  163. Redis.prototype.resetOfflineQueue = function () {
  164. this.offlineQueue = new Deque();
  165. };
  166. Redis.prototype.parseOptions = function () {
  167. this.options = {};
  168. let isTls = false;
  169. for (var i = 0; i < arguments.length; ++i) {
  170. var arg = arguments[i];
  171. if (arg === null || typeof arg === "undefined") {
  172. continue;
  173. }
  174. if (typeof arg === "object") {
  175. lodash_1.defaults(this.options, arg);
  176. }
  177. else if (typeof arg === "string") {
  178. lodash_1.defaults(this.options, utils_1.parseURL(arg));
  179. if (arg.startsWith("rediss://")) {
  180. isTls = true;
  181. }
  182. }
  183. else if (typeof arg === "number") {
  184. this.options.port = arg;
  185. }
  186. else {
  187. throw new Error("Invalid argument " + arg);
  188. }
  189. }
  190. if (isTls) {
  191. lodash_1.defaults(this.options, { tls: true });
  192. }
  193. lodash_1.defaults(this.options, Redis.defaultOptions);
  194. if (typeof this.options.port === "string") {
  195. this.options.port = parseInt(this.options.port, 10);
  196. }
  197. if (typeof this.options.db === "string") {
  198. this.options.db = parseInt(this.options.db, 10);
  199. }
  200. if (this.options.parser === "hiredis") {
  201. console.warn("Hiredis parser is abandoned since ioredis v3.0, and JavaScript parser will be used");
  202. }
  203. };
  204. /**
  205. * Change instance's status
  206. * @private
  207. */
  208. Redis.prototype.setStatus = function (status, arg) {
  209. // @ts-ignore
  210. if (debug.enabled) {
  211. debug("status[%s]: %s -> %s", this._getDescription(), this.status || "[empty]", status);
  212. }
  213. this.status = status;
  214. process.nextTick(this.emit.bind(this, status, arg));
  215. };
  216. /**
  217. * Create a connection to Redis.
  218. * This method will be invoked automatically when creating a new Redis instance
  219. * unless `lazyConnect: true` is passed.
  220. *
  221. * When calling this method manually, a Promise is returned, which will
  222. * be resolved when the connection status is ready.
  223. * @param {function} [callback]
  224. * @return {Promise<void>}
  225. * @public
  226. */
  227. Redis.prototype.connect = function (callback) {
  228. var _Promise = PromiseContainer.get();
  229. var promise = new _Promise((resolve, reject) => {
  230. if (this.status === "connecting" ||
  231. this.status === "connect" ||
  232. this.status === "ready") {
  233. reject(new Error("Redis is already connecting/connected"));
  234. return;
  235. }
  236. this.connectionEpoch += 1;
  237. this.setStatus("connecting");
  238. const { options } = this;
  239. this.condition = {
  240. select: options.db,
  241. auth: options.password,
  242. subscriber: false
  243. };
  244. var _this = this;
  245. standard_as_callback_1.default(this.connector.connect(function (type, err) {
  246. _this.silentEmit(type, err);
  247. }), function (err, stream) {
  248. if (err) {
  249. _this.flushQueue(err);
  250. _this.silentEmit("error", err);
  251. reject(err);
  252. _this.setStatus("end");
  253. return;
  254. }
  255. var CONNECT_EVENT = options.tls ? "secureConnect" : "connect";
  256. if (options.sentinels && !options.enableTLSForSentinelMode) {
  257. CONNECT_EVENT = "connect";
  258. }
  259. _this.stream = stream;
  260. if (typeof options.keepAlive === "number") {
  261. stream.setKeepAlive(true, options.keepAlive);
  262. }
  263. stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this));
  264. stream.once("error", eventHandler.errorHandler(_this));
  265. stream.once("close", eventHandler.closeHandler(_this));
  266. if (options.connectTimeout) {
  267. /*
  268. * Typically, Socket#setTimeout(0) will clear the timer
  269. * set before. However, in some platforms (Electron 3.x~4.x),
  270. * the timer will not be cleared. So we introduce a variable here.
  271. *
  272. * See https://github.com/electron/electron/issues/14915
  273. */
  274. var connectTimeoutCleared = false;
  275. stream.setTimeout(options.connectTimeout, function () {
  276. if (connectTimeoutCleared) {
  277. return;
  278. }
  279. stream.setTimeout(0);
  280. stream.destroy();
  281. var err = new Error("connect ETIMEDOUT");
  282. // @ts-ignore
  283. err.errorno = "ETIMEDOUT";
  284. // @ts-ignore
  285. err.code = "ETIMEDOUT";
  286. // @ts-ignore
  287. err.syscall = "connect";
  288. eventHandler.errorHandler(_this)(err);
  289. });
  290. stream.once(CONNECT_EVENT, function () {
  291. connectTimeoutCleared = true;
  292. stream.setTimeout(0);
  293. });
  294. }
  295. if (options.noDelay) {
  296. stream.setNoDelay(true);
  297. }
  298. var connectionReadyHandler = function () {
  299. _this.removeListener("close", connectionCloseHandler);
  300. resolve();
  301. };
  302. var connectionCloseHandler = function () {
  303. _this.removeListener("ready", connectionReadyHandler);
  304. reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
  305. };
  306. _this.once("ready", connectionReadyHandler);
  307. _this.once("close", connectionCloseHandler);
  308. });
  309. });
  310. return standard_as_callback_1.default(promise, callback);
  311. };
  312. /**
  313. * Disconnect from Redis.
  314. *
  315. * This method closes the connection immediately,
  316. * and may lose some pending replies that haven't written to client.
  317. * If you want to wait for the pending replies, use Redis#quit instead.
  318. * @public
  319. */
  320. Redis.prototype.disconnect = function (reconnect) {
  321. if (!reconnect) {
  322. this.manuallyClosing = true;
  323. }
  324. if (this.reconnectTimeout) {
  325. clearTimeout(this.reconnectTimeout);
  326. this.reconnectTimeout = null;
  327. }
  328. if (this.status === "wait") {
  329. eventHandler.closeHandler(this)();
  330. }
  331. else {
  332. this.connector.disconnect();
  333. }
  334. };
  335. /**
  336. * Disconnect from Redis.
  337. *
  338. * @deprecated
  339. */
  340. Redis.prototype.end = function () {
  341. this.disconnect();
  342. };
  343. /**
  344. * Create a new instance with the same options as the current one.
  345. *
  346. * @example
  347. * ```js
  348. * var redis = new Redis(6380);
  349. * var anotherRedis = redis.duplicate();
  350. * ```
  351. *
  352. * @public
  353. */
  354. Redis.prototype.duplicate = function (override) {
  355. return new Redis(Object.assign({}, this.options, override || {}));
  356. };
  357. Redis.prototype.recoverFromFatalError = function (commandError, err, options) {
  358. this.flushQueue(err, options);
  359. this.silentEmit("error", err);
  360. this.disconnect(true);
  361. };
  362. Redis.prototype.handleReconnection = function handleReconnection(err, item) {
  363. var needReconnect = false;
  364. if (this.options.reconnectOnError) {
  365. needReconnect = this.options.reconnectOnError(err);
  366. }
  367. switch (needReconnect) {
  368. case 1:
  369. case true:
  370. if (this.status !== "reconnecting") {
  371. this.disconnect(true);
  372. }
  373. item.command.reject(err);
  374. break;
  375. case 2:
  376. if (this.status !== "reconnecting") {
  377. this.disconnect(true);
  378. }
  379. if (this.condition.select !== item.select &&
  380. item.command.name !== "select") {
  381. this.select(item.select);
  382. }
  383. this.sendCommand(item.command);
  384. break;
  385. default:
  386. item.command.reject(err);
  387. }
  388. };
  389. /**
  390. * Flush offline queue and command queue with error.
  391. *
  392. * @param {Error} error - The error object to send to the commands
  393. * @param {object} options
  394. * @private
  395. */
  396. Redis.prototype.flushQueue = function (error, options) {
  397. options = lodash_1.defaults({}, options, {
  398. offlineQueue: true,
  399. commandQueue: true
  400. });
  401. var item;
  402. if (options.offlineQueue) {
  403. while (this.offlineQueue.length > 0) {
  404. item = this.offlineQueue.shift();
  405. item.command.reject(error);
  406. }
  407. }
  408. if (options.commandQueue) {
  409. if (this.commandQueue.length > 0) {
  410. if (this.stream) {
  411. this.stream.removeAllListeners("data");
  412. }
  413. while (this.commandQueue.length > 0) {
  414. item = this.commandQueue.shift();
  415. item.command.reject(error);
  416. }
  417. }
  418. }
  419. };
  420. /**
  421. * Check whether Redis has finished loading the persistent data and is able to
  422. * process commands.
  423. *
  424. * @param {Function} callback
  425. * @private
  426. */
  427. Redis.prototype._readyCheck = function (callback) {
  428. var _this = this;
  429. this.info(function (err, res) {
  430. if (err) {
  431. return callback(err);
  432. }
  433. if (typeof res !== "string") {
  434. return callback(null, res);
  435. }
  436. var info = {};
  437. var lines = res.split("\r\n");
  438. for (var i = 0; i < lines.length; ++i) {
  439. var parts = lines[i].split(":");
  440. if (parts[1]) {
  441. info[parts[0]] = parts[1];
  442. }
  443. }
  444. if (!info.loading || info.loading === "0") {
  445. callback(null, info);
  446. }
  447. else {
  448. var loadingEtaMs = (info.loading_eta_seconds || 1) * 1000;
  449. var retryTime = _this.options.maxLoadingRetryTime &&
  450. _this.options.maxLoadingRetryTime < loadingEtaMs
  451. ? _this.options.maxLoadingRetryTime
  452. : loadingEtaMs;
  453. debug("Redis server still loading, trying again in " + retryTime + "ms");
  454. setTimeout(function () {
  455. _this._readyCheck(callback);
  456. }, retryTime);
  457. }
  458. });
  459. };
  460. /**
  461. * Emit only when there's at least one listener.
  462. *
  463. * @param {string} eventName - Event to emit
  464. * @param {...*} arguments - Arguments
  465. * @return {boolean} Returns true if event had listeners, false otherwise.
  466. * @private
  467. */
  468. Redis.prototype.silentEmit = function (eventName) {
  469. var error;
  470. if (eventName === "error") {
  471. error = arguments[1];
  472. if (this.status === "end") {
  473. return;
  474. }
  475. if (this.manuallyClosing) {
  476. // ignore connection related errors when manually disconnecting
  477. if (error instanceof Error &&
  478. (error.message === utils_1.CONNECTION_CLOSED_ERROR_MSG ||
  479. // @ts-ignore
  480. error.syscall === "connect" ||
  481. // @ts-ignore
  482. error.syscall === "read")) {
  483. return;
  484. }
  485. }
  486. }
  487. if (this.listeners(eventName).length > 0) {
  488. return this.emit.apply(this, arguments);
  489. }
  490. if (error && error instanceof Error) {
  491. console.error("[ioredis] Unhandled error event:", error.stack);
  492. }
  493. return false;
  494. };
  495. /**
  496. * Listen for all requests received by the server in real time.
  497. *
  498. * This command will create a new connection to Redis and send a
  499. * MONITOR command via the new connection in order to avoid disturbing
  500. * the current connection.
  501. *
  502. * @param {function} [callback] The callback function. If omit, a promise will be returned.
  503. * @example
  504. * ```js
  505. * var redis = new Redis();
  506. * redis.monitor(function (err, monitor) {
  507. * // Entering monitoring mode.
  508. * monitor.on('monitor', function (time, args, source, database) {
  509. * console.log(time + ": " + util.inspect(args));
  510. * });
  511. * });
  512. *
  513. * // supports promise as well as other commands
  514. * redis.monitor().then(function (monitor) {
  515. * monitor.on('monitor', function (time, args, source, database) {
  516. * console.log(time + ": " + util.inspect(args));
  517. * });
  518. * });
  519. * ```
  520. * @public
  521. */
  522. Redis.prototype.monitor = function (callback) {
  523. var monitorInstance = this.duplicate({
  524. monitor: true,
  525. lazyConnect: false
  526. });
  527. var Promise = PromiseContainer.get();
  528. return standard_as_callback_1.default(new Promise(function (resolve) {
  529. monitorInstance.once("monitoring", function () {
  530. resolve(monitorInstance);
  531. });
  532. }), callback);
  533. };
  534. transaction_1.addTransactionSupport(Redis.prototype);
  535. /**
  536. * Send a command to Redis
  537. *
  538. * This method is used internally by the `Redis#set`, `Redis#lpush` etc.
  539. * Most of the time you won't invoke this method directly.
  540. * However when you want to send a command that is not supported by ioredis yet,
  541. * this command will be useful.
  542. *
  543. * @method sendCommand
  544. * @memberOf Redis#
  545. * @param {Command} command - The Command instance to send.
  546. * @see {@link Command}
  547. * @example
  548. * ```js
  549. * var redis = new Redis();
  550. *
  551. * // Use callback
  552. * var get = new Command('get', ['foo'], 'utf8', function (err, result) {
  553. * console.log(result);
  554. * });
  555. * redis.sendCommand(get);
  556. *
  557. * // Use promise
  558. * var set = new Command('set', ['foo', 'bar'], 'utf8');
  559. * set.promise.then(function (result) {
  560. * console.log(result);
  561. * });
  562. * redis.sendCommand(set);
  563. * ```
  564. * @private
  565. */
  566. Redis.prototype.sendCommand = function (command, stream) {
  567. if (this.status === "wait") {
  568. this.connect().catch(lodash_1.noop);
  569. }
  570. if (this.status === "end") {
  571. command.reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
  572. return command.promise;
  573. }
  574. if (this.condition.subscriber &&
  575. !command_1.default.checkFlag("VALID_IN_SUBSCRIBER_MODE", command.name)) {
  576. command.reject(new Error("Connection in subscriber mode, only subscriber commands may be used"));
  577. return command.promise;
  578. }
  579. var writable = this.status === "ready" ||
  580. (!stream &&
  581. this.status === "connect" &&
  582. commands.hasFlag(command.name, "loading"));
  583. if (!this.stream) {
  584. writable = false;
  585. }
  586. else if (!this.stream.writable) {
  587. writable = false;
  588. }
  589. else if (this.stream._writableState && this.stream._writableState.ended) {
  590. // https://github.com/iojs/io.js/pull/1217
  591. writable = false;
  592. }
  593. if (!writable && !this.options.enableOfflineQueue) {
  594. command.reject(new Error("Stream isn't writeable and enableOfflineQueue options is false"));
  595. return command.promise;
  596. }
  597. if (!writable && command.name === "quit" && this.offlineQueue.length === 0) {
  598. this.disconnect();
  599. command.resolve(Buffer.from("OK"));
  600. return command.promise;
  601. }
  602. if (writable) {
  603. // @ts-ignore
  604. if (debug.enabled) {
  605. debug("write command[%s]: %d -> %s(%o)", this._getDescription(), this.condition.select, command.name, command.args);
  606. }
  607. (stream || this.stream).write(command.toWritable());
  608. this.commandQueue.push({
  609. command: command,
  610. stream: stream,
  611. select: this.condition.select
  612. });
  613. if (command_1.default.checkFlag("WILL_DISCONNECT", command.name)) {
  614. this.manuallyClosing = true;
  615. }
  616. }
  617. else if (this.options.enableOfflineQueue) {
  618. // @ts-ignore
  619. if (debug.enabled) {
  620. debug("queue command[%s]: %d -> %s(%o)", this._getDescription(), this.condition.select, command.name, command.args);
  621. }
  622. this.offlineQueue.push({
  623. command: command,
  624. stream: stream,
  625. select: this.condition.select
  626. });
  627. }
  628. if (command.name === "select" && utils_1.isInt(command.args[0])) {
  629. var db = parseInt(command.args[0], 10);
  630. if (this.condition.select !== db) {
  631. this.condition.select = db;
  632. this.emit("select", db);
  633. debug("switch to db [%d]", this.condition.select);
  634. }
  635. }
  636. return command.promise;
  637. };
  638. /**
  639. * Get description of the connection. Used for debugging.
  640. * @private
  641. */
  642. Redis.prototype._getDescription = function () {
  643. let description;
  644. if (this.options.path) {
  645. description = this.options.path;
  646. }
  647. else if (this.stream &&
  648. this.stream.remoteAddress &&
  649. this.stream.remotePort) {
  650. description = this.stream.remoteAddress + ":" + this.stream.remotePort;
  651. }
  652. else {
  653. description = this.options.host + ":" + this.options.port;
  654. }
  655. if (this.options.connectionName) {
  656. description += ` (${this.options.connectionName})`;
  657. }
  658. return description;
  659. };
  660. [
  661. "scan",
  662. "sscan",
  663. "hscan",
  664. "zscan",
  665. "scanBuffer",
  666. "sscanBuffer",
  667. "hscanBuffer",
  668. "zscanBuffer"
  669. ].forEach(function (command) {
  670. Redis.prototype[command + "Stream"] = function (key, options) {
  671. if (command === "scan" || command === "scanBuffer") {
  672. options = key;
  673. key = null;
  674. }
  675. return new ScanStream_1.default(lodash_1.defaults({
  676. objectMode: true,
  677. key: key,
  678. redis: this,
  679. command: command
  680. }, options));
  681. };
  682. });