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