ioredis-om 5.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1571 -0
  3. package/built/Command.d.ts +166 -0
  4. package/built/Command.js +450 -0
  5. package/built/DataHandler.d.ts +37 -0
  6. package/built/DataHandler.js +224 -0
  7. package/built/Pipeline.d.ts +31 -0
  8. package/built/Pipeline.js +342 -0
  9. package/built/Redis.d.ts +243 -0
  10. package/built/Redis.js +800 -0
  11. package/built/ScanStream.d.ts +23 -0
  12. package/built/ScanStream.js +51 -0
  13. package/built/Script.d.ts +11 -0
  14. package/built/Script.js +62 -0
  15. package/built/SubscriptionSet.d.ts +14 -0
  16. package/built/SubscriptionSet.js +41 -0
  17. package/built/autoPipelining.d.ts +8 -0
  18. package/built/autoPipelining.js +167 -0
  19. package/built/cluster/ClusterOptions.d.ts +172 -0
  20. package/built/cluster/ClusterOptions.js +22 -0
  21. package/built/cluster/ClusterSubscriber.d.ts +29 -0
  22. package/built/cluster/ClusterSubscriber.js +223 -0
  23. package/built/cluster/ClusterSubscriberGroup.d.ts +108 -0
  24. package/built/cluster/ClusterSubscriberGroup.js +373 -0
  25. package/built/cluster/ConnectionPool.d.ts +37 -0
  26. package/built/cluster/ConnectionPool.js +154 -0
  27. package/built/cluster/DelayQueue.d.ts +20 -0
  28. package/built/cluster/DelayQueue.js +53 -0
  29. package/built/cluster/ShardedSubscriber.d.ts +36 -0
  30. package/built/cluster/ShardedSubscriber.js +147 -0
  31. package/built/cluster/index.d.ts +163 -0
  32. package/built/cluster/index.js +937 -0
  33. package/built/cluster/util.d.ts +25 -0
  34. package/built/cluster/util.js +100 -0
  35. package/built/connectors/AbstractConnector.d.ts +12 -0
  36. package/built/connectors/AbstractConnector.js +26 -0
  37. package/built/connectors/ConnectorConstructor.d.ts +5 -0
  38. package/built/connectors/ConnectorConstructor.js +2 -0
  39. package/built/connectors/SentinelConnector/FailoverDetector.d.ts +11 -0
  40. package/built/connectors/SentinelConnector/FailoverDetector.js +45 -0
  41. package/built/connectors/SentinelConnector/SentinelIterator.d.ts +13 -0
  42. package/built/connectors/SentinelConnector/SentinelIterator.js +37 -0
  43. package/built/connectors/SentinelConnector/index.d.ts +72 -0
  44. package/built/connectors/SentinelConnector/index.js +305 -0
  45. package/built/connectors/SentinelConnector/types.d.ts +21 -0
  46. package/built/connectors/SentinelConnector/types.js +2 -0
  47. package/built/connectors/StandaloneConnector.d.ts +17 -0
  48. package/built/connectors/StandaloneConnector.js +69 -0
  49. package/built/connectors/index.d.ts +3 -0
  50. package/built/connectors/index.js +7 -0
  51. package/built/constants/TLSProfiles.d.ts +9 -0
  52. package/built/constants/TLSProfiles.js +149 -0
  53. package/built/errors/ClusterAllFailedError.d.ts +7 -0
  54. package/built/errors/ClusterAllFailedError.js +15 -0
  55. package/built/errors/MaxRetriesPerRequestError.d.ts +5 -0
  56. package/built/errors/MaxRetriesPerRequestError.js +14 -0
  57. package/built/errors/index.d.ts +2 -0
  58. package/built/errors/index.js +5 -0
  59. package/built/index.d.ts +44 -0
  60. package/built/index.js +62 -0
  61. package/built/redis/RedisOptions.d.ts +197 -0
  62. package/built/redis/RedisOptions.js +58 -0
  63. package/built/redis/event_handler.d.ts +4 -0
  64. package/built/redis/event_handler.js +315 -0
  65. package/built/tracing.d.ts +26 -0
  66. package/built/tracing.js +96 -0
  67. package/built/transaction.d.ts +13 -0
  68. package/built/transaction.js +100 -0
  69. package/built/types.d.ts +33 -0
  70. package/built/types.js +2 -0
  71. package/built/utils/Commander.d.ts +50 -0
  72. package/built/utils/Commander.js +117 -0
  73. package/built/utils/RedisCommander.d.ts +8950 -0
  74. package/built/utils/RedisCommander.js +7 -0
  75. package/built/utils/applyMixin.d.ts +3 -0
  76. package/built/utils/applyMixin.js +8 -0
  77. package/built/utils/argumentParsers.d.ts +14 -0
  78. package/built/utils/argumentParsers.js +74 -0
  79. package/built/utils/debug.d.ts +16 -0
  80. package/built/utils/debug.js +95 -0
  81. package/built/utils/index.d.ts +124 -0
  82. package/built/utils/index.js +332 -0
  83. package/built/utils/lodash.d.ts +4 -0
  84. package/built/utils/lodash.js +9 -0
  85. package/package.json +103 -0
@@ -0,0 +1,937 @@
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 redis_errors_1 = require("redis-errors");
6
+ const standard_as_callback_1 = require("standard-as-callback");
7
+ const Command_1 = require("../Command");
8
+ const ClusterAllFailedError_1 = require("../errors/ClusterAllFailedError");
9
+ const Redis_1 = require("../Redis");
10
+ const ScanStream_1 = require("../ScanStream");
11
+ const transaction_1 = require("../transaction");
12
+ const utils_1 = require("../utils");
13
+ const applyMixin_1 = require("../utils/applyMixin");
14
+ const Commander_1 = require("../utils/Commander");
15
+ const ClusterOptions_1 = require("./ClusterOptions");
16
+ const ClusterSubscriber_1 = require("./ClusterSubscriber");
17
+ const ConnectionPool_1 = require("./ConnectionPool");
18
+ const DelayQueue_1 = require("./DelayQueue");
19
+ const util_1 = require("./util");
20
+ const Deque = require("denque");
21
+ const ClusterSubscriberGroup_1 = require("./ClusterSubscriberGroup");
22
+ const debug = (0, utils_1.Debug)("cluster");
23
+ const REJECT_OVERWRITTEN_COMMANDS = new WeakSet();
24
+ /**
25
+ * Client for the official Redis Cluster
26
+ */
27
+ class Cluster extends Commander_1.default {
28
+ /**
29
+ * Creates an instance of Cluster.
30
+ */
31
+ //TODO: Add an option that enables or disables sharded PubSub
32
+ constructor(startupNodes, options = {}) {
33
+ super();
34
+ this.slots = [];
35
+ /**
36
+ * @ignore
37
+ */
38
+ this._groupsIds = {};
39
+ /**
40
+ * @ignore
41
+ */
42
+ this._groupsBySlot = Array(16384);
43
+ /**
44
+ * @ignore
45
+ */
46
+ this.isCluster = true;
47
+ this.retryAttempts = 0;
48
+ this.delayQueue = new DelayQueue_1.default();
49
+ this.offlineQueue = new Deque();
50
+ this.isRefreshing = false;
51
+ this._refreshSlotsCacheCallbacks = [];
52
+ this._autoPipelines = new Map();
53
+ this._runningAutoPipelines = new Set();
54
+ this._readyDelayedCallbacks = [];
55
+ /**
56
+ * Every time Cluster#connect() is called, this value will be
57
+ * auto-incrementing. The purpose of this value is used for
58
+ * discarding previous connect attampts when creating a new
59
+ * connection.
60
+ */
61
+ this.connectionEpoch = 0;
62
+ events_1.EventEmitter.call(this);
63
+ this.startupNodes = startupNodes;
64
+ this.options = (0, utils_1.defaults)({}, options, ClusterOptions_1.DEFAULT_CLUSTER_OPTIONS, this.options);
65
+ if (this.options.shardedSubscribers) {
66
+ this.createShardedSubscriberGroup();
67
+ }
68
+ if (this.options.redisOptions &&
69
+ this.options.redisOptions.keyPrefix &&
70
+ !this.options.keyPrefix) {
71
+ this.options.keyPrefix = this.options.redisOptions.keyPrefix;
72
+ }
73
+ // validate options
74
+ if (typeof this.options.scaleReads !== "function" &&
75
+ ["all", "master", "slave"].indexOf(this.options.scaleReads) === -1) {
76
+ throw new Error('Invalid option scaleReads "' +
77
+ this.options.scaleReads +
78
+ '". Expected "all", "master", "slave" or a custom function');
79
+ }
80
+ this.connectionPool = new ConnectionPool_1.default(this.options.redisOptions);
81
+ this.connectionPool.on("-node", (redis, key) => {
82
+ this.emit("-node", redis);
83
+ });
84
+ this.connectionPool.on("+node", (redis) => {
85
+ this.emit("+node", redis);
86
+ });
87
+ this.connectionPool.on("drain", () => {
88
+ this.setStatus("close");
89
+ });
90
+ this.connectionPool.on("nodeError", (error, key) => {
91
+ this.emit("node error", error, key);
92
+ });
93
+ this.subscriber = new ClusterSubscriber_1.default(this.connectionPool, this);
94
+ if (this.options.scripts) {
95
+ Object.entries(this.options.scripts).forEach(([name, definition]) => {
96
+ this.defineCommand(name, definition);
97
+ });
98
+ }
99
+ if (this.options.lazyConnect) {
100
+ this.setStatus("wait");
101
+ }
102
+ else {
103
+ this.connect().catch((err) => {
104
+ debug("connecting failed: %s", err);
105
+ });
106
+ }
107
+ }
108
+ /**
109
+ * Connect to a cluster
110
+ */
111
+ connect() {
112
+ return new Promise((resolve, reject) => {
113
+ if (this.status === "connecting" ||
114
+ this.status === "connect" ||
115
+ this.status === "ready") {
116
+ reject(new Error("Redis is already connecting/connected"));
117
+ return;
118
+ }
119
+ const epoch = ++this.connectionEpoch;
120
+ this.setStatus("connecting");
121
+ this.resolveStartupNodeHostnames()
122
+ .then((nodes) => {
123
+ if (this.connectionEpoch !== epoch) {
124
+ debug("discard connecting after resolving startup nodes because epoch not match: %d != %d", epoch, this.connectionEpoch);
125
+ reject(new redis_errors_1.RedisError("Connection is discarded because a new connection is made"));
126
+ return;
127
+ }
128
+ if (this.status !== "connecting") {
129
+ debug("discard connecting after resolving startup nodes because the status changed to %s", this.status);
130
+ reject(new redis_errors_1.RedisError("Connection is aborted"));
131
+ return;
132
+ }
133
+ this.connectionPool.reset(nodes);
134
+ if (this.options.shardedSubscribers) {
135
+ this.shardedSubscribers
136
+ .reset(this.slots, this.connectionPool.getNodes("all"))
137
+ .catch((err) => {
138
+ // TODO should we emit an error event here?
139
+ debug("Error while starting subscribers: %s", err);
140
+ });
141
+ }
142
+ const readyHandler = () => {
143
+ this.setStatus("ready");
144
+ this.retryAttempts = 0;
145
+ this.executeOfflineCommands();
146
+ this.resetNodesRefreshInterval();
147
+ resolve();
148
+ };
149
+ let closeListener = undefined;
150
+ const refreshListener = () => {
151
+ this.invokeReadyDelayedCallbacks(undefined);
152
+ this.removeListener("close", closeListener);
153
+ this.manuallyClosing = false;
154
+ this.setStatus("connect");
155
+ if (this.options.enableReadyCheck) {
156
+ this.readyCheck((err, fail) => {
157
+ if (err || fail) {
158
+ debug("Ready check failed (%s). Reconnecting...", err || fail);
159
+ if (this.status === "connect") {
160
+ this.disconnect(true);
161
+ }
162
+ }
163
+ else {
164
+ readyHandler();
165
+ }
166
+ });
167
+ }
168
+ else {
169
+ readyHandler();
170
+ }
171
+ };
172
+ closeListener = () => {
173
+ const error = new Error("None of startup nodes is available");
174
+ this.removeListener("refresh", refreshListener);
175
+ this.invokeReadyDelayedCallbacks(error);
176
+ reject(error);
177
+ };
178
+ this.once("refresh", refreshListener);
179
+ this.once("close", closeListener);
180
+ this.once("close", this.handleCloseEvent.bind(this));
181
+ this.refreshSlotsCache((err) => {
182
+ if (err && err.message === ClusterAllFailedError_1.default.defaultMessage) {
183
+ Redis_1.default.prototype.silentEmit.call(this, "error", err);
184
+ this.connectionPool.reset([]);
185
+ }
186
+ });
187
+ this.subscriber.start();
188
+ if (this.options.shardedSubscribers) {
189
+ this.shardedSubscribers.start().catch((err) => {
190
+ // TODO should we emit an error event here?
191
+ debug("Error while starting subscribers: %s", err);
192
+ });
193
+ }
194
+ })
195
+ .catch((err) => {
196
+ this.setStatus("close");
197
+ this.handleCloseEvent(err);
198
+ this.invokeReadyDelayedCallbacks(err);
199
+ reject(err);
200
+ });
201
+ });
202
+ }
203
+ /**
204
+ * Disconnect from every node in the cluster.
205
+ */
206
+ disconnect(reconnect = false) {
207
+ const status = this.status;
208
+ this.setStatus("disconnecting");
209
+ if (!reconnect) {
210
+ this.manuallyClosing = true;
211
+ }
212
+ if (this.reconnectTimeout && !reconnect) {
213
+ clearTimeout(this.reconnectTimeout);
214
+ this.reconnectTimeout = null;
215
+ debug("Canceled reconnecting attempts");
216
+ }
217
+ this.clearNodesRefreshInterval();
218
+ this.subscriber.stop();
219
+ if (this.options.shardedSubscribers) {
220
+ this.shardedSubscribers.stop();
221
+ }
222
+ if (status === "wait") {
223
+ this.setStatus("close");
224
+ this.handleCloseEvent();
225
+ }
226
+ else {
227
+ this.connectionPool.reset([]);
228
+ }
229
+ }
230
+ /**
231
+ * Quit the cluster gracefully.
232
+ */
233
+ quit(callback) {
234
+ const status = this.status;
235
+ this.setStatus("disconnecting");
236
+ this.manuallyClosing = true;
237
+ if (this.reconnectTimeout) {
238
+ clearTimeout(this.reconnectTimeout);
239
+ this.reconnectTimeout = null;
240
+ }
241
+ this.clearNodesRefreshInterval();
242
+ this.subscriber.stop();
243
+ if (this.options.shardedSubscribers) {
244
+ this.shardedSubscribers.stop();
245
+ }
246
+ if (status === "wait") {
247
+ const ret = (0, standard_as_callback_1.default)(Promise.resolve("OK"), callback);
248
+ // use setImmediate to make sure "close" event
249
+ // being emitted after quit() is returned
250
+ setImmediate(function () {
251
+ this.setStatus("close");
252
+ this.handleCloseEvent();
253
+ }.bind(this));
254
+ return ret;
255
+ }
256
+ return (0, standard_as_callback_1.default)(Promise.all(this.nodes().map((node) => node.quit().catch((err) => {
257
+ // Ignore the error caused by disconnecting since
258
+ // we're disconnecting...
259
+ if (err.message === utils_1.CONNECTION_CLOSED_ERROR_MSG) {
260
+ return "OK";
261
+ }
262
+ throw err;
263
+ }))).then(() => "OK"), callback);
264
+ }
265
+ /**
266
+ * Create a new instance with the same startup nodes and options as the current one.
267
+ *
268
+ * @example
269
+ * ```js
270
+ * var cluster = new Redis.Cluster([{ host: "127.0.0.1", port: "30001" }]);
271
+ * var anotherCluster = cluster.duplicate();
272
+ * ```
273
+ */
274
+ duplicate(overrideStartupNodes = [], overrideOptions = {}) {
275
+ const startupNodes = overrideStartupNodes.length > 0
276
+ ? overrideStartupNodes
277
+ : this.startupNodes.slice(0);
278
+ const options = Object.assign({}, this.options, overrideOptions);
279
+ return new Cluster(startupNodes, options);
280
+ }
281
+ /**
282
+ * Get nodes with the specified role
283
+ */
284
+ nodes(role = "all") {
285
+ if (role !== "all" && role !== "master" && role !== "slave") {
286
+ throw new Error('Invalid role "' + role + '". Expected "all", "master" or "slave"');
287
+ }
288
+ return this.connectionPool.getNodes(role);
289
+ }
290
+ /**
291
+ * This is needed in order not to install a listener for each auto pipeline
292
+ *
293
+ * @ignore
294
+ */
295
+ delayUntilReady(callback) {
296
+ this._readyDelayedCallbacks.push(callback);
297
+ }
298
+ /**
299
+ * Get the number of commands queued in automatic pipelines.
300
+ *
301
+ * This is not available (and returns 0) until the cluster is connected and slots information have been received.
302
+ */
303
+ get autoPipelineQueueSize() {
304
+ let queued = 0;
305
+ for (const pipeline of this._autoPipelines.values()) {
306
+ queued += pipeline.length;
307
+ }
308
+ return queued;
309
+ }
310
+ /**
311
+ * Refresh the slot cache
312
+ *
313
+ * @ignore
314
+ */
315
+ refreshSlotsCache(callback) {
316
+ if (callback) {
317
+ this._refreshSlotsCacheCallbacks.push(callback);
318
+ }
319
+ if (this.isRefreshing) {
320
+ return;
321
+ }
322
+ this.isRefreshing = true;
323
+ const _this = this;
324
+ const wrapper = (error) => {
325
+ this.isRefreshing = false;
326
+ for (const callback of this._refreshSlotsCacheCallbacks) {
327
+ callback(error);
328
+ }
329
+ this._refreshSlotsCacheCallbacks = [];
330
+ };
331
+ const nodes = (0, utils_1.shuffle)(this.connectionPool.getNodes());
332
+ let lastNodeError = null;
333
+ function tryNode(index) {
334
+ if (index === nodes.length) {
335
+ const error = new ClusterAllFailedError_1.default(ClusterAllFailedError_1.default.defaultMessage, lastNodeError);
336
+ return wrapper(error);
337
+ }
338
+ const node = nodes[index];
339
+ const key = `${node.options.host}:${node.options.port}`;
340
+ debug("getting slot cache from %s", key);
341
+ _this.getInfoFromNode(node, function (err) {
342
+ switch (_this.status) {
343
+ case "close":
344
+ case "end":
345
+ return wrapper(new Error("Cluster is disconnected."));
346
+ case "disconnecting":
347
+ return wrapper(new Error("Cluster is disconnecting."));
348
+ }
349
+ if (err) {
350
+ _this.emit("node error", err, key);
351
+ lastNodeError = err;
352
+ tryNode(index + 1);
353
+ }
354
+ else {
355
+ _this.emit("refresh");
356
+ wrapper();
357
+ }
358
+ });
359
+ }
360
+ tryNode(0);
361
+ }
362
+ /**
363
+ * @ignore
364
+ */
365
+ sendCommand(command, stream, node) {
366
+ if (this.status === "wait") {
367
+ this.connect().catch(utils_1.noop);
368
+ }
369
+ if (this.status === "end") {
370
+ command.reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
371
+ return command.promise;
372
+ }
373
+ let to = this.options.scaleReads;
374
+ if (to !== "master") {
375
+ const isCommandReadOnly = command.isReadOnly ||
376
+ ((0, commands_1.exists)(command.name) && (0, commands_1.hasFlag)(command.name, "readonly"));
377
+ if (!isCommandReadOnly) {
378
+ to = "master";
379
+ }
380
+ }
381
+ let targetSlot = node ? node.slot : command.getSlot();
382
+ const ttl = {};
383
+ const _this = this;
384
+ if (!node && !REJECT_OVERWRITTEN_COMMANDS.has(command)) {
385
+ REJECT_OVERWRITTEN_COMMANDS.add(command);
386
+ const reject = command.reject;
387
+ command.reject = function (err) {
388
+ const partialTry = tryConnection.bind(null, true);
389
+ _this.handleError(err, ttl, {
390
+ moved: function (slot, key) {
391
+ debug("command %s is moved to %s", command.name, key);
392
+ targetSlot = Number(slot);
393
+ if (_this.slots[slot]) {
394
+ _this.slots[slot][0] = key;
395
+ }
396
+ else {
397
+ _this.slots[slot] = [key];
398
+ }
399
+ _this._groupsBySlot[slot] =
400
+ _this._groupsIds[_this.slots[slot].join(";")];
401
+ _this.connectionPool.findOrCreate(_this.natMapper(key));
402
+ tryConnection();
403
+ debug("refreshing slot caches... (triggered by MOVED error)");
404
+ _this.refreshSlotsCache();
405
+ },
406
+ ask: function (slot, key) {
407
+ debug("command %s is required to ask %s:%s", command.name, key);
408
+ const mapped = _this.natMapper(key);
409
+ _this.connectionPool.findOrCreate(mapped);
410
+ tryConnection(false, `${mapped.host}:${mapped.port}`);
411
+ },
412
+ tryagain: partialTry,
413
+ clusterDown: partialTry,
414
+ connectionClosed: partialTry,
415
+ maxRedirections: function (redirectionError) {
416
+ reject.call(command, redirectionError);
417
+ },
418
+ defaults: function () {
419
+ reject.call(command, err);
420
+ },
421
+ });
422
+ };
423
+ }
424
+ tryConnection();
425
+ function tryConnection(random, asking) {
426
+ if (_this.status === "end") {
427
+ command.reject(new redis_errors_1.AbortError("Cluster is ended."));
428
+ return;
429
+ }
430
+ let redis;
431
+ if (_this.status === "ready" || command.name === "cluster") {
432
+ if (node && node.redis) {
433
+ redis = node.redis;
434
+ }
435
+ else if (Command_1.default.checkFlag("ENTER_SUBSCRIBER_MODE", command.name) ||
436
+ Command_1.default.checkFlag("EXIT_SUBSCRIBER_MODE", command.name)) {
437
+ if (_this.options.shardedSubscribers &&
438
+ (command.name == "ssubscribe" || command.name == "sunsubscribe")) {
439
+ const sub = _this.shardedSubscribers.getResponsibleSubscriber(targetSlot);
440
+ if (!sub) {
441
+ command.reject(new redis_errors_1.AbortError(`No sharded subscriber for slot: ${targetSlot}`));
442
+ return;
443
+ }
444
+ let status = -1;
445
+ if (command.name == "ssubscribe") {
446
+ status = _this.shardedSubscribers.addChannels(command.getKeys());
447
+ }
448
+ if (command.name == "sunsubscribe") {
449
+ status = _this.shardedSubscribers.removeChannels(command.getKeys());
450
+ }
451
+ if (status !== -1) {
452
+ redis = sub.getInstance();
453
+ }
454
+ else {
455
+ command.reject(new redis_errors_1.AbortError("Possible CROSSSLOT error: All channels must hash to the same slot"));
456
+ }
457
+ }
458
+ else {
459
+ redis = _this.subscriber.getInstance();
460
+ }
461
+ if (!redis) {
462
+ command.reject(new redis_errors_1.AbortError("No subscriber for the cluster"));
463
+ return;
464
+ }
465
+ }
466
+ else {
467
+ if (!random) {
468
+ if (typeof targetSlot === "number" && _this.slots[targetSlot]) {
469
+ const nodeKeys = _this.slots[targetSlot];
470
+ if (typeof to === "function") {
471
+ const nodes = nodeKeys.map(function (key) {
472
+ return _this.connectionPool.getInstanceByKey(key);
473
+ });
474
+ redis = to(nodes, command);
475
+ if (Array.isArray(redis)) {
476
+ redis = (0, utils_1.sample)(redis);
477
+ }
478
+ if (!redis) {
479
+ redis = nodes[0];
480
+ }
481
+ }
482
+ else {
483
+ let key;
484
+ if (to === "all") {
485
+ key = (0, utils_1.sample)(nodeKeys);
486
+ }
487
+ else if (to === "slave" && nodeKeys.length > 1) {
488
+ key = (0, utils_1.sample)(nodeKeys, 1);
489
+ }
490
+ else {
491
+ key = nodeKeys[0];
492
+ }
493
+ redis = _this.connectionPool.getInstanceByKey(key);
494
+ }
495
+ }
496
+ if (asking) {
497
+ redis = _this.connectionPool.getInstanceByKey(asking);
498
+ redis.asking();
499
+ }
500
+ }
501
+ if (!redis) {
502
+ redis =
503
+ (typeof to === "function"
504
+ ? null
505
+ : _this.connectionPool.getSampleInstance(to)) ||
506
+ _this.connectionPool.getSampleInstance("all");
507
+ }
508
+ }
509
+ if (node && !node.redis) {
510
+ node.redis = redis;
511
+ }
512
+ }
513
+ if (redis) {
514
+ redis.sendCommand(command, stream);
515
+ }
516
+ else if (_this.options.enableOfflineQueue) {
517
+ _this.offlineQueue.push({
518
+ command: command,
519
+ stream: stream,
520
+ node: node,
521
+ });
522
+ }
523
+ else {
524
+ command.reject(new Error("Cluster isn't ready and enableOfflineQueue options is false"));
525
+ }
526
+ }
527
+ return command.promise;
528
+ }
529
+ sscanStream(key, options) {
530
+ return this.createScanStream("sscan", { key, options });
531
+ }
532
+ sscanBufferStream(key, options) {
533
+ return this.createScanStream("sscanBuffer", { key, options });
534
+ }
535
+ hscanStream(key, options) {
536
+ return this.createScanStream("hscan", { key, options });
537
+ }
538
+ hscanBufferStream(key, options) {
539
+ return this.createScanStream("hscanBuffer", { key, options });
540
+ }
541
+ zscanStream(key, options) {
542
+ return this.createScanStream("zscan", { key, options });
543
+ }
544
+ zscanBufferStream(key, options) {
545
+ return this.createScanStream("zscanBuffer", { key, options });
546
+ }
547
+ /**
548
+ * @ignore
549
+ */
550
+ handleError(error, ttl, handlers) {
551
+ if (typeof ttl.value === "undefined") {
552
+ ttl.value = this.options.maxRedirections;
553
+ }
554
+ else {
555
+ ttl.value -= 1;
556
+ }
557
+ if (ttl.value <= 0) {
558
+ handlers.maxRedirections(new Error("Too many Cluster redirections. Last error: " + error));
559
+ return;
560
+ }
561
+ const errv = error.message.split(" ");
562
+ if (errv[0] === "MOVED") {
563
+ const timeout = this.options.retryDelayOnMoved;
564
+ if (timeout && typeof timeout === "number") {
565
+ this.delayQueue.push("moved", handlers.moved.bind(null, errv[1], errv[2]), { timeout });
566
+ }
567
+ else {
568
+ handlers.moved(errv[1], errv[2]);
569
+ }
570
+ }
571
+ else if (errv[0] === "ASK") {
572
+ handlers.ask(errv[1], errv[2]);
573
+ }
574
+ else if (errv[0] === "TRYAGAIN") {
575
+ this.delayQueue.push("tryagain", handlers.tryagain, {
576
+ timeout: this.options.retryDelayOnTryAgain,
577
+ });
578
+ }
579
+ else if (errv[0] === "CLUSTERDOWN" &&
580
+ this.options.retryDelayOnClusterDown > 0) {
581
+ this.delayQueue.push("clusterdown", handlers.connectionClosed, {
582
+ timeout: this.options.retryDelayOnClusterDown,
583
+ callback: this.refreshSlotsCache.bind(this),
584
+ });
585
+ }
586
+ else if (error.message === utils_1.CONNECTION_CLOSED_ERROR_MSG &&
587
+ this.options.retryDelayOnFailover > 0 &&
588
+ this.status === "ready") {
589
+ this.delayQueue.push("failover", handlers.connectionClosed, {
590
+ timeout: this.options.retryDelayOnFailover,
591
+ callback: this.refreshSlotsCache.bind(this),
592
+ });
593
+ }
594
+ else {
595
+ handlers.defaults();
596
+ }
597
+ }
598
+ resetOfflineQueue() {
599
+ this.offlineQueue = new Deque();
600
+ }
601
+ clearNodesRefreshInterval() {
602
+ if (this.slotsTimer) {
603
+ clearTimeout(this.slotsTimer);
604
+ this.slotsTimer = null;
605
+ }
606
+ }
607
+ resetNodesRefreshInterval() {
608
+ if (this.slotsTimer || !this.options.slotsRefreshInterval) {
609
+ return;
610
+ }
611
+ const nextRound = () => {
612
+ this.slotsTimer = setTimeout(() => {
613
+ debug('refreshing slot caches... (triggered by "slotsRefreshInterval" option)');
614
+ this.refreshSlotsCache(() => {
615
+ nextRound();
616
+ });
617
+ }, this.options.slotsRefreshInterval);
618
+ };
619
+ nextRound();
620
+ }
621
+ /**
622
+ * Change cluster instance's status
623
+ */
624
+ setStatus(status) {
625
+ debug("status: %s -> %s", this.status || "[empty]", status);
626
+ this.status = status;
627
+ process.nextTick(() => {
628
+ this.emit(status);
629
+ });
630
+ }
631
+ /**
632
+ * Called when closed to check whether a reconnection should be made
633
+ */
634
+ handleCloseEvent(reason) {
635
+ var _a;
636
+ if (reason) {
637
+ debug("closed because %s", reason);
638
+ }
639
+ let retryDelay;
640
+ if (!this.manuallyClosing &&
641
+ typeof this.options.clusterRetryStrategy === "function") {
642
+ retryDelay = this.options.clusterRetryStrategy.call(this, ++this.retryAttempts, reason);
643
+ }
644
+ if (typeof retryDelay === "number") {
645
+ this.setStatus("reconnecting");
646
+ this.reconnectTimeout = setTimeout(() => {
647
+ this.reconnectTimeout = null;
648
+ debug("Cluster is disconnected. Retrying after %dms", retryDelay);
649
+ this.connect().catch(function (err) {
650
+ debug("Got error %s when reconnecting. Ignoring...", err);
651
+ });
652
+ }, retryDelay);
653
+ }
654
+ else {
655
+ if (this.options.shardedSubscribers) {
656
+ (_a = this.subscriberGroupEmitter) === null || _a === void 0 ? void 0 : _a.removeAllListeners();
657
+ }
658
+ this.setStatus("end");
659
+ this.flushQueue(new Error("None of startup nodes is available"));
660
+ }
661
+ }
662
+ /**
663
+ * Flush offline queue with error.
664
+ */
665
+ flushQueue(error) {
666
+ let item;
667
+ while ((item = this.offlineQueue.shift())) {
668
+ item.command.reject(error);
669
+ }
670
+ }
671
+ executeOfflineCommands() {
672
+ if (this.offlineQueue.length) {
673
+ debug("send %d commands in offline queue", this.offlineQueue.length);
674
+ const offlineQueue = this.offlineQueue;
675
+ this.resetOfflineQueue();
676
+ let item;
677
+ while ((item = offlineQueue.shift())) {
678
+ this.sendCommand(item.command, item.stream, item.node);
679
+ }
680
+ }
681
+ }
682
+ natMapper(nodeKey) {
683
+ const key = typeof nodeKey === "string"
684
+ ? nodeKey
685
+ : `${nodeKey.host}:${nodeKey.port}`;
686
+ let mapped = null;
687
+ if (this.options.natMap && typeof this.options.natMap === "function") {
688
+ mapped = this.options.natMap(key);
689
+ }
690
+ else if (this.options.natMap && typeof this.options.natMap === "object") {
691
+ mapped = this.options.natMap[key];
692
+ }
693
+ if (mapped) {
694
+ debug("NAT mapping %s -> %O", key, mapped);
695
+ return Object.assign({}, mapped);
696
+ }
697
+ return typeof nodeKey === "string"
698
+ ? (0, util_1.nodeKeyToRedisOptions)(nodeKey)
699
+ : nodeKey;
700
+ }
701
+ getInfoFromNode(redis, callback) {
702
+ if (!redis) {
703
+ return callback(new Error("Node is disconnected"));
704
+ }
705
+ // Use a duplication of the connection to avoid
706
+ // timeouts when the connection is in the blocking
707
+ // mode (e.g. waiting for BLPOP).
708
+ const duplicatedConnection = redis.duplicate({
709
+ enableOfflineQueue: true,
710
+ enableReadyCheck: false,
711
+ retryStrategy: null,
712
+ connectionName: (0, util_1.getConnectionName)("refresher", this.options.redisOptions && this.options.redisOptions.connectionName),
713
+ });
714
+ // Ignore error events since we will handle
715
+ // exceptions for the CLUSTER SLOTS command.
716
+ duplicatedConnection.on("error", utils_1.noop);
717
+ duplicatedConnection.cluster("SLOTS", (0, utils_1.timeout)((err, result) => {
718
+ duplicatedConnection.disconnect();
719
+ if (err) {
720
+ debug("error encountered running CLUSTER.SLOTS: %s", err);
721
+ return callback(err);
722
+ }
723
+ if (this.status === "disconnecting" ||
724
+ this.status === "close" ||
725
+ this.status === "end") {
726
+ debug("ignore CLUSTER.SLOTS results (count: %d) since cluster status is %s", result.length, this.status);
727
+ callback();
728
+ return;
729
+ }
730
+ const nodes = [];
731
+ debug("cluster slots result count: %d", result.length);
732
+ for (let i = 0; i < result.length; ++i) {
733
+ const items = result[i];
734
+ const slotRangeStart = items[0];
735
+ const slotRangeEnd = items[1];
736
+ const keys = [];
737
+ for (let j = 2; j < items.length; j++) {
738
+ if (!items[j][0]) {
739
+ continue;
740
+ }
741
+ const node = this.natMapper({
742
+ host: items[j][0],
743
+ port: items[j][1],
744
+ });
745
+ node.readOnly = j !== 2;
746
+ nodes.push(node);
747
+ keys.push(node.host + ":" + node.port);
748
+ }
749
+ debug("cluster slots result [%d]: slots %d~%d served by %s", i, slotRangeStart, slotRangeEnd, keys);
750
+ for (let slot = slotRangeStart; slot <= slotRangeEnd; slot++) {
751
+ this.slots[slot] = keys;
752
+ }
753
+ }
754
+ // Assign to each node keys a numeric value to make autopipeline comparison faster.
755
+ this._groupsIds = Object.create(null);
756
+ let j = 0;
757
+ for (let i = 0; i < 16384; i++) {
758
+ const target = (this.slots[i] || []).join(";");
759
+ if (!target.length) {
760
+ this._groupsBySlot[i] = undefined;
761
+ continue;
762
+ }
763
+ if (!this._groupsIds[target]) {
764
+ this._groupsIds[target] = ++j;
765
+ }
766
+ this._groupsBySlot[i] = this._groupsIds[target];
767
+ }
768
+ this.connectionPool.reset(nodes);
769
+ if (this.options.shardedSubscribers) {
770
+ this.shardedSubscribers
771
+ .reset(this.slots, this.connectionPool.getNodes("all"))
772
+ .catch((err) => {
773
+ // TODO should we emit an error event here?
774
+ debug("Error while starting subscribers: %s", err);
775
+ });
776
+ }
777
+ callback();
778
+ }, this.options.slotsRefreshTimeout));
779
+ }
780
+ invokeReadyDelayedCallbacks(err) {
781
+ for (const c of this._readyDelayedCallbacks) {
782
+ process.nextTick(c, err);
783
+ }
784
+ this._readyDelayedCallbacks = [];
785
+ }
786
+ /**
787
+ * Check whether Cluster is able to process commands
788
+ */
789
+ readyCheck(callback) {
790
+ this.cluster("INFO", (err, res) => {
791
+ if (err) {
792
+ return callback(err);
793
+ }
794
+ if (typeof res !== "string") {
795
+ return callback();
796
+ }
797
+ let state;
798
+ const lines = res.split("\r\n");
799
+ for (let i = 0; i < lines.length; ++i) {
800
+ const parts = lines[i].split(":");
801
+ if (parts[0] === "cluster_state") {
802
+ state = parts[1];
803
+ break;
804
+ }
805
+ }
806
+ if (state === "fail") {
807
+ debug("cluster state not ok (%s)", state);
808
+ callback(null, state);
809
+ }
810
+ else {
811
+ callback();
812
+ }
813
+ });
814
+ }
815
+ resolveSrv(hostname) {
816
+ return new Promise((resolve, reject) => {
817
+ this.options.resolveSrv(hostname, (err, records) => {
818
+ if (err) {
819
+ return reject(err);
820
+ }
821
+ const self = this, groupedRecords = (0, util_1.groupSrvRecords)(records), sortedKeys = Object.keys(groupedRecords).sort((a, b) => parseInt(a) - parseInt(b));
822
+ function tryFirstOne(err) {
823
+ if (!sortedKeys.length) {
824
+ return reject(err);
825
+ }
826
+ const key = sortedKeys[0], group = groupedRecords[key], record = (0, util_1.weightSrvRecords)(group);
827
+ if (!group.records.length) {
828
+ sortedKeys.shift();
829
+ }
830
+ self.dnsLookup(record.name).then((host) => resolve({
831
+ host,
832
+ port: record.port,
833
+ }), tryFirstOne);
834
+ }
835
+ tryFirstOne();
836
+ });
837
+ });
838
+ }
839
+ dnsLookup(hostname) {
840
+ return new Promise((resolve, reject) => {
841
+ this.options.dnsLookup(hostname, (err, address) => {
842
+ if (err) {
843
+ debug("failed to resolve hostname %s to IP: %s", hostname, err.message);
844
+ reject(err);
845
+ }
846
+ else {
847
+ debug("resolved hostname %s to IP %s", hostname, address);
848
+ resolve(address);
849
+ }
850
+ });
851
+ });
852
+ }
853
+ /**
854
+ * Normalize startup nodes, and resolving hostnames to IPs.
855
+ *
856
+ * This process happens every time when #connect() is called since
857
+ * #startupNodes and DNS records may chanage.
858
+ */
859
+ async resolveStartupNodeHostnames() {
860
+ if (!Array.isArray(this.startupNodes) || this.startupNodes.length === 0) {
861
+ throw new Error("`startupNodes` should contain at least one node.");
862
+ }
863
+ const startupNodes = (0, util_1.normalizeNodeOptions)(this.startupNodes);
864
+ const hostnames = (0, util_1.getUniqueHostnamesFromOptions)(startupNodes);
865
+ if (hostnames.length === 0) {
866
+ return startupNodes;
867
+ }
868
+ const configs = await Promise.all(hostnames.map((this.options.useSRVRecords ? this.resolveSrv : this.dnsLookup).bind(this)));
869
+ const hostnameToConfig = (0, utils_1.zipMap)(hostnames, configs);
870
+ return startupNodes.map((node) => {
871
+ const config = hostnameToConfig.get(node.host);
872
+ if (!config) {
873
+ return node;
874
+ }
875
+ if (this.options.useSRVRecords) {
876
+ return Object.assign({}, node, config);
877
+ }
878
+ return Object.assign({}, node, { host: config });
879
+ });
880
+ }
881
+ createScanStream(command, { key, options = {} }) {
882
+ return new ScanStream_1.default({
883
+ objectMode: true,
884
+ key: key,
885
+ redis: this,
886
+ command: command,
887
+ ...options,
888
+ });
889
+ }
890
+ createShardedSubscriberGroup() {
891
+ this.subscriberGroupEmitter = new events_1.EventEmitter();
892
+ this.shardedSubscribers = new ClusterSubscriberGroup_1.default(this.subscriberGroupEmitter, this.options);
893
+ // Error handler used only for sharded-subscriber-triggered slots cache refreshes.
894
+ // Normal (non-subscriber) connections are created with lazyConnect: true and can
895
+ // become zombied. For sharded subscribers, a ClusterAllFailedError means
896
+ // we have lost all nodes from the subscriber perspective and must tear down.
897
+ const refreshSlotsCacheCallback = (err) => {
898
+ // Disconnect only when refreshing the slots cache fails with ClusterAllFailedError
899
+ if (err instanceof ClusterAllFailedError_1.default) {
900
+ this.disconnect(true);
901
+ }
902
+ };
903
+ this.subscriberGroupEmitter.on("-node", (redis, nodeKey) => {
904
+ this.emit("-node", redis, nodeKey);
905
+ this.refreshSlotsCache(refreshSlotsCacheCallback);
906
+ });
907
+ this.subscriberGroupEmitter.on("subscriberConnectFailed", ({ delay, error }) => {
908
+ this.emit("error", error);
909
+ setTimeout(() => {
910
+ this.refreshSlotsCache(refreshSlotsCacheCallback);
911
+ }, delay);
912
+ });
913
+ this.subscriberGroupEmitter.on("moved", () => {
914
+ this.refreshSlotsCache(refreshSlotsCacheCallback);
915
+ });
916
+ this.subscriberGroupEmitter.on("-subscriber", () => {
917
+ this.emit("-subscriber");
918
+ });
919
+ this.subscriberGroupEmitter.on("+subscriber", () => {
920
+ this.emit("+subscriber");
921
+ });
922
+ this.subscriberGroupEmitter.on("nodeError", (error, nodeKey) => {
923
+ this.emit("nodeError", error, nodeKey);
924
+ });
925
+ this.subscriberGroupEmitter.on("subscribersReady", () => {
926
+ this.emit("subscribersReady");
927
+ });
928
+ for (const event of ["smessage", "smessageBuffer"]) {
929
+ this.subscriberGroupEmitter.on(event, (arg1, arg2, arg3) => {
930
+ this.emit(event, arg1, arg2, arg3);
931
+ });
932
+ }
933
+ }
934
+ }
935
+ (0, applyMixin_1.default)(Cluster, events_1.EventEmitter);
936
+ (0, transaction_1.addTransactionSupport)(Cluster.prototype);
937
+ exports.default = Cluster;