Redis.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const commands_1 = require("@ioredis/commands");
  4. const events_1 = require("events");
  5. const standard_as_callback_1 = require("standard-as-callback");
  6. const cluster_1 = require("./cluster");
  7. const Command_1 = require("./Command");
  8. const connectors_1 = require("./connectors");
  9. const SentinelConnector_1 = require("./connectors/SentinelConnector");
  10. const eventHandler = require("./redis/event_handler");
  11. const RedisOptions_1 = require("./redis/RedisOptions");
  12. const ScanStream_1 = require("./ScanStream");
  13. const transaction_1 = require("./transaction");
  14. const utils_1 = require("./utils");
  15. const applyMixin_1 = require("./utils/applyMixin");
  16. const Commander_1 = require("./utils/Commander");
  17. const lodash_1 = require("./utils/lodash");
  18. const Deque = require("denque");
  19. const debug = (0, utils_1.Debug)("redis");
  20. /**
  21. * This is the major component of ioredis.
  22. * Use it to connect to a standalone Redis server or Sentinels.
  23. *
  24. * ```typescript
  25. * const redis = new Redis(); // Default port is 6379
  26. * async function main() {
  27. * redis.set("foo", "bar");
  28. * redis.get("foo", (err, result) => {
  29. * // `result` should be "bar"
  30. * console.log(err, result);
  31. * });
  32. * // Or use Promise
  33. * const result = await redis.get("foo");
  34. * }
  35. * ```
  36. */
  37. class Redis extends Commander_1.default {
  38. constructor(arg1, arg2, arg3) {
  39. super();
  40. this.status = "wait";
  41. /**
  42. * @ignore
  43. */
  44. this.isCluster = false;
  45. this.reconnectTimeout = null;
  46. this.connectionEpoch = 0;
  47. this.retryAttempts = 0;
  48. this.manuallyClosing = false;
  49. // Prepare autopipelines structures
  50. this._autoPipelines = new Map();
  51. this._runningAutoPipelines = new Set();
  52. this.parseOptions(arg1, arg2, arg3);
  53. events_1.EventEmitter.call(this);
  54. this.resetCommandQueue();
  55. this.resetOfflineQueue();
  56. if (this.options.Connector) {
  57. this.connector = new this.options.Connector(this.options);
  58. }
  59. else if (this.options.sentinels) {
  60. const sentinelConnector = new SentinelConnector_1.default(this.options);
  61. sentinelConnector.emitter = this;
  62. this.connector = sentinelConnector;
  63. }
  64. else {
  65. this.connector = new connectors_1.StandaloneConnector(this.options);
  66. }
  67. if (this.options.scripts) {
  68. Object.entries(this.options.scripts).forEach(([name, definition]) => {
  69. this.defineCommand(name, definition);
  70. });
  71. }
  72. // end(or wait) -> connecting -> connect -> ready -> end
  73. if (this.options.lazyConnect) {
  74. this.setStatus("wait");
  75. }
  76. else {
  77. this.connect().catch(lodash_1.noop);
  78. }
  79. }
  80. /**
  81. * Create a Redis instance.
  82. * This is the same as `new Redis()` but is included for compatibility with node-redis.
  83. */
  84. static createClient(...args) {
  85. return new Redis(...args);
  86. }
  87. get autoPipelineQueueSize() {
  88. let queued = 0;
  89. for (const pipeline of this._autoPipelines.values()) {
  90. queued += pipeline.length;
  91. }
  92. return queued;
  93. }
  94. /**
  95. * Create a connection to Redis.
  96. * This method will be invoked automatically when creating a new Redis instance
  97. * unless `lazyConnect: true` is passed.
  98. *
  99. * When calling this method manually, a Promise is returned, which will
  100. * be resolved when the connection status is ready. The promise can reject
  101. * if the connection fails, times out, or if Redis is already connecting/connected.
  102. */
  103. connect(callback) {
  104. const promise = new Promise((resolve, reject) => {
  105. if (this.status === "connecting" ||
  106. this.status === "connect" ||
  107. this.status === "ready") {
  108. reject(new Error("Redis is already connecting/connected"));
  109. return;
  110. }
  111. this.connectionEpoch += 1;
  112. this.setStatus("connecting");
  113. const { options } = this;
  114. this.condition = {
  115. select: options.db,
  116. auth: options.username
  117. ? [options.username, options.password]
  118. : options.password,
  119. subscriber: false,
  120. };
  121. const _this = this;
  122. (0, standard_as_callback_1.default)(this.connector.connect(function (type, err) {
  123. _this.silentEmit(type, err);
  124. }), function (err, stream) {
  125. if (err) {
  126. _this.flushQueue(err);
  127. _this.silentEmit("error", err);
  128. reject(err);
  129. _this.setStatus("end");
  130. return;
  131. }
  132. let CONNECT_EVENT = options.tls ? "secureConnect" : "connect";
  133. if ("sentinels" in options &&
  134. options.sentinels &&
  135. !options.enableTLSForSentinelMode) {
  136. CONNECT_EVENT = "connect";
  137. }
  138. _this.stream = stream;
  139. if (options.noDelay) {
  140. stream.setNoDelay(true);
  141. }
  142. // Node ignores setKeepAlive before connect, therefore we wait for the event:
  143. // https://github.com/nodejs/node/issues/31663
  144. if (typeof options.keepAlive === "number") {
  145. if (stream.connecting) {
  146. stream.once(CONNECT_EVENT, () => {
  147. stream.setKeepAlive(true, options.keepAlive);
  148. });
  149. }
  150. else {
  151. stream.setKeepAlive(true, options.keepAlive);
  152. }
  153. }
  154. if (stream.connecting) {
  155. stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this));
  156. if (options.connectTimeout) {
  157. /*
  158. * Typically, Socket#setTimeout(0) will clear the timer
  159. * set before. However, in some platforms (Electron 3.x~4.x),
  160. * the timer will not be cleared. So we introduce a variable here.
  161. *
  162. * See https://github.com/electron/electron/issues/14915
  163. */
  164. let connectTimeoutCleared = false;
  165. stream.setTimeout(options.connectTimeout, function () {
  166. if (connectTimeoutCleared) {
  167. return;
  168. }
  169. stream.setTimeout(0);
  170. stream.destroy();
  171. const err = new Error("connect ETIMEDOUT");
  172. // @ts-expect-error
  173. err.errorno = "ETIMEDOUT";
  174. // @ts-expect-error
  175. err.code = "ETIMEDOUT";
  176. // @ts-expect-error
  177. err.syscall = "connect";
  178. eventHandler.errorHandler(_this)(err);
  179. });
  180. stream.once(CONNECT_EVENT, function () {
  181. connectTimeoutCleared = true;
  182. stream.setTimeout(0);
  183. });
  184. }
  185. }
  186. else if (stream.destroyed) {
  187. const firstError = _this.connector.firstError;
  188. if (firstError) {
  189. process.nextTick(() => {
  190. eventHandler.errorHandler(_this)(firstError);
  191. });
  192. }
  193. process.nextTick(eventHandler.closeHandler(_this));
  194. }
  195. else {
  196. process.nextTick(eventHandler.connectHandler(_this));
  197. }
  198. if (!stream.destroyed) {
  199. stream.once("error", eventHandler.errorHandler(_this));
  200. stream.once("close", eventHandler.closeHandler(_this));
  201. }
  202. const connectionReadyHandler = function () {
  203. _this.removeListener("close", connectionCloseHandler);
  204. resolve();
  205. };
  206. var connectionCloseHandler = function () {
  207. _this.removeListener("ready", connectionReadyHandler);
  208. reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
  209. };
  210. _this.once("ready", connectionReadyHandler);
  211. _this.once("close", connectionCloseHandler);
  212. });
  213. });
  214. return (0, standard_as_callback_1.default)(promise, callback);
  215. }
  216. /**
  217. * Disconnect from Redis.
  218. *
  219. * This method closes the connection immediately,
  220. * and may lose some pending replies that haven't written to client.
  221. * If you want to wait for the pending replies, use Redis#quit instead.
  222. */
  223. disconnect(reconnect = false) {
  224. if (!reconnect) {
  225. this.manuallyClosing = true;
  226. }
  227. if (this.reconnectTimeout && !reconnect) {
  228. clearTimeout(this.reconnectTimeout);
  229. this.reconnectTimeout = null;
  230. }
  231. if (this.status === "wait") {
  232. eventHandler.closeHandler(this)();
  233. }
  234. else {
  235. this.connector.disconnect();
  236. }
  237. }
  238. /**
  239. * Disconnect from Redis.
  240. *
  241. * @deprecated
  242. */
  243. end() {
  244. this.disconnect();
  245. }
  246. /**
  247. * Create a new instance with the same options as the current one.
  248. *
  249. * @example
  250. * ```js
  251. * var redis = new Redis(6380);
  252. * var anotherRedis = redis.duplicate();
  253. * ```
  254. */
  255. duplicate(override) {
  256. return new Redis({ ...this.options, ...override });
  257. }
  258. /**
  259. * Mode of the connection.
  260. *
  261. * One of `"normal"`, `"subscriber"`, or `"monitor"`. When the connection is
  262. * not in `"normal"` mode, certain commands are not allowed.
  263. */
  264. get mode() {
  265. var _a;
  266. return this.options.monitor
  267. ? "monitor"
  268. : ((_a = this.condition) === null || _a === void 0 ? void 0 : _a.subscriber)
  269. ? "subscriber"
  270. : "normal";
  271. }
  272. /**
  273. * Listen for all requests received by the server in real time.
  274. *
  275. * This command will create a new connection to Redis and send a
  276. * MONITOR command via the new connection in order to avoid disturbing
  277. * the current connection.
  278. *
  279. * @param callback The callback function. If omit, a promise will be returned.
  280. * @example
  281. * ```js
  282. * var redis = new Redis();
  283. * redis.monitor(function (err, monitor) {
  284. * // Entering monitoring mode.
  285. * monitor.on('monitor', function (time, args, source, database) {
  286. * console.log(time + ": " + util.inspect(args));
  287. * });
  288. * });
  289. *
  290. * // supports promise as well as other commands
  291. * redis.monitor().then(function (monitor) {
  292. * monitor.on('monitor', function (time, args, source, database) {
  293. * console.log(time + ": " + util.inspect(args));
  294. * });
  295. * });
  296. * ```
  297. */
  298. monitor(callback) {
  299. const monitorInstance = this.duplicate({
  300. monitor: true,
  301. lazyConnect: false,
  302. });
  303. return (0, standard_as_callback_1.default)(new Promise(function (resolve, reject) {
  304. monitorInstance.once("error", reject);
  305. monitorInstance.once("monitoring", function () {
  306. resolve(monitorInstance);
  307. });
  308. }), callback);
  309. }
  310. /**
  311. * Send a command to Redis
  312. *
  313. * This method is used internally and in most cases you should not
  314. * use it directly. If you need to send a command that is not supported
  315. * by the library, you can use the `call` method:
  316. *
  317. * ```js
  318. * const redis = new Redis();
  319. *
  320. * redis.call('set', 'foo', 'bar');
  321. * // or
  322. * redis.call(['set', 'foo', 'bar']);
  323. * ```
  324. *
  325. * @ignore
  326. */
  327. sendCommand(command, stream) {
  328. var _a, _b;
  329. if (this.status === "wait") {
  330. this.connect().catch(lodash_1.noop);
  331. }
  332. if (this.status === "end") {
  333. command.reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
  334. return command.promise;
  335. }
  336. if (((_a = this.condition) === null || _a === void 0 ? void 0 : _a.subscriber) &&
  337. !Command_1.default.checkFlag("VALID_IN_SUBSCRIBER_MODE", command.name)) {
  338. command.reject(new Error("Connection in subscriber mode, only subscriber commands may be used"));
  339. return command.promise;
  340. }
  341. if (typeof this.options.commandTimeout === "number") {
  342. command.setTimeout(this.options.commandTimeout);
  343. }
  344. const blockingTimeout = this.getBlockingTimeoutInMs(command);
  345. let writable = this.status === "ready" ||
  346. (!stream &&
  347. this.status === "connect" &&
  348. (0, commands_1.exists)(command.name, { caseInsensitive: true }) &&
  349. ((0, commands_1.hasFlag)(command.name, "loading", { nameCaseInsensitive: true }) ||
  350. Command_1.default.checkFlag("HANDSHAKE_COMMANDS", command.name)));
  351. if (!this.stream) {
  352. writable = false;
  353. }
  354. else if (!this.stream.writable) {
  355. writable = false;
  356. // @ts-expect-error
  357. }
  358. else if (this.stream._writableState && this.stream._writableState.ended) {
  359. // TODO: We should be able to remove this as the PR has already been merged.
  360. // https://github.com/iojs/io.js/pull/1217
  361. writable = false;
  362. }
  363. if (!writable) {
  364. if (!this.options.enableOfflineQueue) {
  365. command.reject(new Error("Stream isn't writeable and enableOfflineQueue options is false"));
  366. return command.promise;
  367. }
  368. if (command.name === "quit" && this.offlineQueue.length === 0) {
  369. this.disconnect();
  370. command.resolve(Buffer.from("OK"));
  371. return command.promise;
  372. }
  373. // @ts-expect-error
  374. if (debug.enabled) {
  375. debug("queue command[%s]: %d -> %s(%o)", this._getDescription(), this.condition.select, command.name, command.args);
  376. }
  377. this.offlineQueue.push({
  378. command: command,
  379. stream: stream,
  380. select: this.condition.select,
  381. });
  382. // For blocking commands in the offline queue, arm a client-side timeout
  383. // only when blockingTimeout is configured. Without this option, queued
  384. // blocking commands may wait indefinitely on a dead connection.
  385. if (Command_1.default.checkFlag("BLOCKING_COMMANDS", command.name)) {
  386. const offlineTimeout = this.getConfiguredBlockingTimeout();
  387. if (offlineTimeout !== undefined) {
  388. command.setBlockingTimeout(offlineTimeout);
  389. }
  390. }
  391. }
  392. else {
  393. // @ts-expect-error
  394. if (debug.enabled) {
  395. debug("write command[%s]: %d -> %s(%o)", this._getDescription(), (_b = this.condition) === null || _b === void 0 ? void 0 : _b.select, command.name, command.args);
  396. }
  397. if (stream) {
  398. if ("isPipeline" in stream && stream.isPipeline) {
  399. stream.write(command.toWritable(stream.destination.redis.stream));
  400. }
  401. else {
  402. stream.write(command.toWritable(stream));
  403. }
  404. }
  405. else {
  406. this.stream.write(command.toWritable(this.stream));
  407. }
  408. this.commandQueue.push({
  409. command: command,
  410. stream: stream,
  411. select: this.condition.select,
  412. });
  413. if (blockingTimeout !== undefined) {
  414. command.setBlockingTimeout(blockingTimeout);
  415. }
  416. if (Command_1.default.checkFlag("WILL_DISCONNECT", command.name)) {
  417. this.manuallyClosing = true;
  418. }
  419. if (this.options.socketTimeout !== undefined && this.socketTimeoutTimer === undefined) {
  420. this.setSocketTimeout();
  421. }
  422. }
  423. if (command.name === "select" && (0, utils_1.isInt)(command.args[0])) {
  424. const db = parseInt(command.args[0], 10);
  425. if (this.condition.select !== db) {
  426. this.condition.select = db;
  427. this.emit("select", db);
  428. debug("switch to db [%d]", this.condition.select);
  429. }
  430. }
  431. return command.promise;
  432. }
  433. getBlockingTimeoutInMs(command) {
  434. var _a;
  435. if (!Command_1.default.checkFlag("BLOCKING_COMMANDS", command.name)) {
  436. return undefined;
  437. }
  438. // Feature is opt-in: only enabled when blockingTimeout is set to a positive number
  439. const configuredTimeout = this.getConfiguredBlockingTimeout();
  440. if (configuredTimeout === undefined) {
  441. return undefined;
  442. }
  443. const timeout = command.extractBlockingTimeout();
  444. if (typeof timeout === "number") {
  445. if (timeout > 0) {
  446. // Finite timeout from command args - add grace period
  447. return timeout + ((_a = this.options.blockingTimeoutGrace) !== null && _a !== void 0 ? _a : RedisOptions_1.DEFAULT_REDIS_OPTIONS.blockingTimeoutGrace);
  448. }
  449. // Command has timeout=0 (block forever), use blockingTimeout option as safety net
  450. return configuredTimeout;
  451. }
  452. if (timeout === null) {
  453. // No BLOCK option found (e.g., XREAD without BLOCK), use blockingTimeout as safety net
  454. return configuredTimeout;
  455. }
  456. return undefined;
  457. }
  458. getConfiguredBlockingTimeout() {
  459. if (typeof this.options.blockingTimeout === "number" &&
  460. this.options.blockingTimeout > 0) {
  461. return this.options.blockingTimeout;
  462. }
  463. return undefined;
  464. }
  465. setSocketTimeout() {
  466. this.socketTimeoutTimer = setTimeout(() => {
  467. this.stream.destroy(new Error(`Socket timeout. Expecting data, but didn't receive any in ${this.options.socketTimeout}ms.`));
  468. this.socketTimeoutTimer = undefined;
  469. }, this.options.socketTimeout);
  470. // this handler must run after the "data" handler in "DataHandler"
  471. // so that `this.commandQueue.length` will be updated
  472. this.stream.once("data", () => {
  473. clearTimeout(this.socketTimeoutTimer);
  474. this.socketTimeoutTimer = undefined;
  475. if (this.commandQueue.length === 0)
  476. return;
  477. this.setSocketTimeout();
  478. });
  479. }
  480. scanStream(options) {
  481. return this.createScanStream("scan", { options });
  482. }
  483. scanBufferStream(options) {
  484. return this.createScanStream("scanBuffer", { options });
  485. }
  486. sscanStream(key, options) {
  487. return this.createScanStream("sscan", { key, options });
  488. }
  489. sscanBufferStream(key, options) {
  490. return this.createScanStream("sscanBuffer", { key, options });
  491. }
  492. hscanStream(key, options) {
  493. return this.createScanStream("hscan", { key, options });
  494. }
  495. hscanBufferStream(key, options) {
  496. return this.createScanStream("hscanBuffer", { key, options });
  497. }
  498. zscanStream(key, options) {
  499. return this.createScanStream("zscan", { key, options });
  500. }
  501. zscanBufferStream(key, options) {
  502. return this.createScanStream("zscanBuffer", { key, options });
  503. }
  504. /**
  505. * Emit only when there's at least one listener.
  506. *
  507. * @ignore
  508. */
  509. silentEmit(eventName, arg) {
  510. let error;
  511. if (eventName === "error") {
  512. error = arg;
  513. if (this.status === "end") {
  514. return;
  515. }
  516. if (this.manuallyClosing) {
  517. // ignore connection related errors when manually disconnecting
  518. if (error instanceof Error &&
  519. (error.message === utils_1.CONNECTION_CLOSED_ERROR_MSG ||
  520. // @ts-expect-error
  521. error.syscall === "connect" ||
  522. // @ts-expect-error
  523. error.syscall === "read")) {
  524. return;
  525. }
  526. }
  527. }
  528. if (this.listeners(eventName).length > 0) {
  529. return this.emit.apply(this, arguments);
  530. }
  531. if (error && error instanceof Error) {
  532. console.error("[ioredis] Unhandled error event:", error.stack);
  533. }
  534. return false;
  535. }
  536. /**
  537. * @ignore
  538. */
  539. recoverFromFatalError(_commandError, err, options) {
  540. this.flushQueue(err, options);
  541. this.silentEmit("error", err);
  542. this.disconnect(true);
  543. }
  544. /**
  545. * @ignore
  546. */
  547. handleReconnection(err, item) {
  548. var _a;
  549. let needReconnect = false;
  550. if (this.options.reconnectOnError &&
  551. !Command_1.default.checkFlag("IGNORE_RECONNECT_ON_ERROR", item.command.name)) {
  552. needReconnect = this.options.reconnectOnError(err);
  553. }
  554. switch (needReconnect) {
  555. case 1:
  556. case true:
  557. if (this.status !== "reconnecting") {
  558. this.disconnect(true);
  559. }
  560. item.command.reject(err);
  561. break;
  562. case 2:
  563. if (this.status !== "reconnecting") {
  564. this.disconnect(true);
  565. }
  566. if (((_a = this.condition) === null || _a === void 0 ? void 0 : _a.select) !== item.select &&
  567. item.command.name !== "select") {
  568. this.select(item.select);
  569. }
  570. // TODO
  571. // @ts-expect-error
  572. this.sendCommand(item.command);
  573. break;
  574. default:
  575. item.command.reject(err);
  576. }
  577. }
  578. /**
  579. * Get description of the connection. Used for debugging.
  580. */
  581. _getDescription() {
  582. let description;
  583. if ("path" in this.options && this.options.path) {
  584. description = this.options.path;
  585. }
  586. else if (this.stream &&
  587. this.stream.remoteAddress &&
  588. this.stream.remotePort) {
  589. description = this.stream.remoteAddress + ":" + this.stream.remotePort;
  590. }
  591. else if ("host" in this.options && this.options.host) {
  592. description = this.options.host + ":" + this.options.port;
  593. }
  594. else {
  595. // Unexpected
  596. description = "";
  597. }
  598. if (this.options.connectionName) {
  599. description += ` (${this.options.connectionName})`;
  600. }
  601. return description;
  602. }
  603. resetCommandQueue() {
  604. this.commandQueue = new Deque();
  605. }
  606. resetOfflineQueue() {
  607. this.offlineQueue = new Deque();
  608. }
  609. parseOptions(...args) {
  610. const options = {};
  611. let isTls = false;
  612. for (let i = 0; i < args.length; ++i) {
  613. const arg = args[i];
  614. if (arg === null || typeof arg === "undefined") {
  615. continue;
  616. }
  617. if (typeof arg === "object") {
  618. (0, lodash_1.defaults)(options, arg);
  619. }
  620. else if (typeof arg === "string") {
  621. (0, lodash_1.defaults)(options, (0, utils_1.parseURL)(arg));
  622. if (arg.startsWith("rediss://")) {
  623. isTls = true;
  624. }
  625. }
  626. else if (typeof arg === "number") {
  627. options.port = arg;
  628. }
  629. else {
  630. throw new Error("Invalid argument " + arg);
  631. }
  632. }
  633. if (isTls) {
  634. (0, lodash_1.defaults)(options, { tls: true });
  635. }
  636. (0, lodash_1.defaults)(options, Redis.defaultOptions);
  637. if (typeof options.port === "string") {
  638. options.port = parseInt(options.port, 10);
  639. }
  640. if (typeof options.db === "string") {
  641. options.db = parseInt(options.db, 10);
  642. }
  643. // @ts-expect-error
  644. this.options = (0, utils_1.resolveTLSProfile)(options);
  645. }
  646. /**
  647. * Change instance's status
  648. */
  649. setStatus(status, arg) {
  650. // @ts-expect-error
  651. if (debug.enabled) {
  652. debug("status[%s]: %s -> %s", this._getDescription(), this.status || "[empty]", status);
  653. }
  654. this.status = status;
  655. process.nextTick(this.emit.bind(this, status, arg));
  656. }
  657. createScanStream(command, { key, options = {} }) {
  658. return new ScanStream_1.default({
  659. objectMode: true,
  660. key: key,
  661. redis: this,
  662. command: command,
  663. ...options,
  664. });
  665. }
  666. /**
  667. * Flush offline queue and command queue with error.
  668. *
  669. * @param error The error object to send to the commands
  670. * @param options options
  671. */
  672. flushQueue(error, options) {
  673. options = (0, lodash_1.defaults)({}, options, {
  674. offlineQueue: true,
  675. commandQueue: true,
  676. });
  677. let item;
  678. if (options.offlineQueue) {
  679. while ((item = this.offlineQueue.shift())) {
  680. item.command.reject(error);
  681. }
  682. }
  683. if (options.commandQueue) {
  684. if (this.commandQueue.length > 0) {
  685. if (this.stream) {
  686. this.stream.removeAllListeners("data");
  687. }
  688. while ((item = this.commandQueue.shift())) {
  689. item.command.reject(error);
  690. }
  691. }
  692. }
  693. }
  694. /**
  695. * Check whether Redis has finished loading the persistent data and is able to
  696. * process commands.
  697. */
  698. _readyCheck(callback) {
  699. const _this = this;
  700. this.info(function (err, res) {
  701. if (err) {
  702. if (err.message && err.message.includes("NOPERM")) {
  703. console.warn(`Skipping the ready check because INFO command fails: "${err.message}". You can disable ready check with "enableReadyCheck". More: https://github.com/luin/ioredis/wiki/Disable-ready-check.`);
  704. return callback(null, {});
  705. }
  706. return callback(err);
  707. }
  708. if (typeof res !== "string") {
  709. return callback(null, res);
  710. }
  711. const info = {};
  712. const lines = res.split("\r\n");
  713. for (let i = 0; i < lines.length; ++i) {
  714. const [fieldName, ...fieldValueParts] = lines[i].split(":");
  715. const fieldValue = fieldValueParts.join(":");
  716. if (fieldValue) {
  717. info[fieldName] = fieldValue;
  718. }
  719. }
  720. if (!info.loading || info.loading === "0") {
  721. callback(null, info);
  722. }
  723. else {
  724. const loadingEtaMs = (info.loading_eta_seconds || 1) * 1000;
  725. const retryTime = _this.options.maxLoadingRetryTime &&
  726. _this.options.maxLoadingRetryTime < loadingEtaMs
  727. ? _this.options.maxLoadingRetryTime
  728. : loadingEtaMs;
  729. debug("Redis server still loading, trying again in " + retryTime + "ms");
  730. setTimeout(function () {
  731. _this._readyCheck(callback);
  732. }, retryTime);
  733. }
  734. }).catch(lodash_1.noop);
  735. }
  736. }
  737. Redis.Cluster = cluster_1.default;
  738. Redis.Command = Command_1.default;
  739. /**
  740. * Default options
  741. */
  742. Redis.defaultOptions = RedisOptions_1.DEFAULT_REDIS_OPTIONS;
  743. (0, applyMixin_1.default)(Redis, events_1.EventEmitter);
  744. (0, transaction_1.addTransactionSupport)(Redis.prototype);
  745. exports.default = Redis;