Review funkt
This commit is contained in:
248
node_modules/ioredis/built/redis/RedisOptions.d.ts
generated
vendored
Normal file
248
node_modules/ioredis/built/redis/RedisOptions.d.ts
generated
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
import { CommanderOptions } from "../utils/Commander";
|
||||
import ConnectorConstructor from "../connectors/ConnectorConstructor";
|
||||
import { SentinelConnectionOptions } from "../connectors/SentinelConnector";
|
||||
import { StandaloneConnectionOptions } from "../connectors/StandaloneConnector";
|
||||
import { ProtocolVersion, ReplyMappingMode } from "../types";
|
||||
import type { HimportFieldset } from "../himport/types";
|
||||
export type ReconnectOnError = (err: Error) => boolean | 1 | 2;
|
||||
export type RetryStrategy = ((times: number) => number | void | null) | null | undefined;
|
||||
export interface CommonRedisOptions extends CommanderOptions {
|
||||
Connector?: ConnectorConstructor | undefined;
|
||||
/**
|
||||
* Determines the delay in milliseconds before reconnecting after a connection loss.
|
||||
*
|
||||
* @default Exponential backoff capped at 5000ms, plus 0-199ms of random jitter.
|
||||
*/
|
||||
retryStrategy?: RetryStrategy;
|
||||
/**
|
||||
* If a command does not return a reply within a set number of milliseconds,
|
||||
* a "Command timed out" error will be thrown.
|
||||
*/
|
||||
commandTimeout?: number | undefined;
|
||||
/**
|
||||
* Enables client-side timeout protection for blocking commands when set
|
||||
* to a positive number. If `blockingTimeout` is undefined, `0`, or
|
||||
* negative (e.g. `-1`), the protection is disabled and no client-side
|
||||
* timers are installed for blocking commands.
|
||||
*/
|
||||
blockingTimeout?: number | undefined;
|
||||
/**
|
||||
* Grace period (ms) added to blocking command timeouts. Only used when
|
||||
* `blockingTimeout` is a positive number. Defaults to 100ms.
|
||||
*/
|
||||
blockingTimeoutGrace?: number | undefined;
|
||||
/**
|
||||
* If the socket does not receive data within a set number of milliseconds:
|
||||
* 1. the socket is considered "dead" and will be destroyed
|
||||
* 2. the client will reject any running commands (altought they might have been processed by the server)
|
||||
* 3. the reconnect strategy will kick in (depending on the configuration)
|
||||
*/
|
||||
socketTimeout?: number | undefined;
|
||||
/**
|
||||
* Initial delay in milliseconds before the first TCP keep-alive probe.
|
||||
* @link https://nodejs.org/api/net.html#socketsetkeepaliveenable-initialdelay
|
||||
* @default 30000
|
||||
*/
|
||||
keepAlive?: number | undefined;
|
||||
/**
|
||||
* Enable/disable the use of Nagle's algorithm.
|
||||
* @link https://nodejs.org/api/net.html#socketsetnodelaynodelay
|
||||
* @default true
|
||||
*/
|
||||
noDelay?: boolean | undefined;
|
||||
/**
|
||||
* Set the name of the connection to make it easier to identity the connection
|
||||
* in client list.
|
||||
* @link https://redis.io/commands/client-setname
|
||||
*/
|
||||
connectionName?: string | undefined;
|
||||
/**
|
||||
* If true, skips setting library info via CLIENT SETINFO.
|
||||
* @link https://redis.io/docs/latest/commands/client-setinfo/
|
||||
* @default false
|
||||
*/
|
||||
disableClientInfo?: boolean | undefined;
|
||||
/**
|
||||
* Tag to append to the library name in CLIENT SETINFO (ioredis(tag)).
|
||||
* @link https://redis.io/docs/latest/commands/client-setinfo/
|
||||
* @default undefined
|
||||
*/
|
||||
clientInfoTag?: string | undefined;
|
||||
/**
|
||||
* If set, client will send AUTH command with the value of this option as the first argument when connected.
|
||||
* This is supported since Redis 6.
|
||||
*/
|
||||
username?: string | undefined;
|
||||
/**
|
||||
* If set, client will send AUTH command with the value of this option when connected.
|
||||
*/
|
||||
password?: string | undefined;
|
||||
/**
|
||||
* Database index to use.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
db?: number | undefined;
|
||||
/**
|
||||
* When the client reconnects, channels subscribed in the previous connection will be
|
||||
* resubscribed automatically if `autoResubscribe` is `true`.
|
||||
* @default true
|
||||
*/
|
||||
autoResubscribe?: boolean | undefined;
|
||||
/**
|
||||
* Whether or not to resend unfulfilled commands on reconnect.
|
||||
* Unfulfilled commands are most likely to be blocking commands such as `brpop` or `blpop`.
|
||||
* @default true
|
||||
*/
|
||||
autoResendUnfulfilledCommands?: boolean | undefined;
|
||||
/**
|
||||
* Whether or not to reconnect on certain Redis errors.
|
||||
* This options by default is `null`, which means it should never reconnect on Redis errors.
|
||||
* You can pass a function that accepts an Redis error, and returns:
|
||||
* - `true` or `1` to trigger a reconnection.
|
||||
* - `false` or `0` to not reconnect.
|
||||
* - `2` to reconnect and resend the failed command (who triggered the error) after reconnection.
|
||||
* @example
|
||||
* ```js
|
||||
* const redis = new Redis({
|
||||
* reconnectOnError(err) {
|
||||
* const targetError = "READONLY";
|
||||
* if (err.message.includes(targetError)) {
|
||||
* // Only reconnect when the error contains "READONLY"
|
||||
* return true; // or `return 1;`
|
||||
* }
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
* @default null
|
||||
*/
|
||||
reconnectOnError?: ReconnectOnError | null | undefined;
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
readOnly?: boolean | undefined;
|
||||
/**
|
||||
* When enabled, numbers returned by Redis will be converted to JavaScript strings instead of numbers.
|
||||
* This is necessary if you want to handle big numbers (above `Number.MAX_SAFE_INTEGER` === 2^53).
|
||||
* @default false
|
||||
*/
|
||||
stringNumbers?: boolean | undefined;
|
||||
/**
|
||||
* The RESP protocol version to use.
|
||||
* @default 3
|
||||
*/
|
||||
protocol?: ProtocolVersion | undefined;
|
||||
/**
|
||||
* How RESP3-only reply types are represented in JavaScript.
|
||||
* Only supported when `protocol` is 3.
|
||||
*
|
||||
* - `"legacy"` (default): RESP2-compatible shapes. Map replies arrive as
|
||||
* flat `[key, value, ...]` arrays and doubles as strings, so replies are
|
||||
* identical across both protocols.
|
||||
* - `"resp3"`: map replies arrive as plain objects (with string keys) and
|
||||
* doubles as numbers.
|
||||
*
|
||||
* @default "legacy"
|
||||
*/
|
||||
replyMapping?: ReplyMappingMode | undefined;
|
||||
/**
|
||||
* How long the client will wait before killing a socket due to inactivity during initial connection.
|
||||
* @default 10000
|
||||
*/
|
||||
connectTimeout?: number | undefined;
|
||||
/**
|
||||
* This option is used internally when you call `redis.monitor()` to tell Redis
|
||||
* to enter the monitor mode when the connection is established.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
monitor?: boolean | undefined;
|
||||
/**
|
||||
* The commands that don't get a reply due to the connection to the server is lost are
|
||||
* put into a queue and will be resent on reconnect (if allowed by the `retryStrategy` option).
|
||||
* This option is used to configure how many reconnection attempts should be allowed before
|
||||
* the queue is flushed with a `MaxRetriesPerRequestError` error.
|
||||
* Set this options to `null` instead of a number to let commands wait forever
|
||||
* until the connection is alive again.
|
||||
*
|
||||
* @default 20
|
||||
*/
|
||||
maxRetriesPerRequest?: number | null | undefined;
|
||||
/**
|
||||
* @default 10000
|
||||
*/
|
||||
maxLoadingRetryTime?: number | undefined;
|
||||
/**
|
||||
* @default false
|
||||
*/
|
||||
enableAutoPipelining?: boolean | undefined;
|
||||
/**
|
||||
* @default []
|
||||
*/
|
||||
autoPipeliningIgnoredCommands?: string[] | undefined;
|
||||
offlineQueue?: boolean | undefined;
|
||||
commandQueue?: boolean | undefined;
|
||||
/**
|
||||
*
|
||||
* By default, if the connection to Redis server has not been established, commands are added to a queue
|
||||
* and are executed once the connection is "ready" (when `enableReadyCheck` is true, "ready" means
|
||||
* the Redis server has loaded the database from disk, otherwise means the connection to the Redis
|
||||
* server has been established). If this option is false, when execute the command when the connection
|
||||
* isn't ready, an error will be returned.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enableOfflineQueue?: boolean | undefined;
|
||||
/**
|
||||
* The client will sent an INFO command to check whether the server is still loading data from the disk (
|
||||
* which happens when the server is just launched) when the connection is established, and only wait until
|
||||
* the loading process is finished before emitting the `ready` event.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enableReadyCheck?: boolean | undefined;
|
||||
/**
|
||||
* When a Redis instance is initialized, a connection to the server is immediately established. Set this to
|
||||
* true will delay the connection to the server until the first command is sent or `redis.connect()` is called
|
||||
* explicitly. When `redis.connect()` is called explicitly, a Promise is returned, which will be resolved
|
||||
* when the connection is ready or rejected when it fails. The rejection should be handled by the user.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
lazyConnect?: boolean | undefined;
|
||||
/**
|
||||
* @default undefined
|
||||
*/
|
||||
scripts?: Record<string, {
|
||||
lua: string;
|
||||
numberOfKeys?: number | undefined;
|
||||
readOnly?: boolean | undefined;
|
||||
}> | undefined;
|
||||
/**
|
||||
* Managed-fieldset support is experimental and requires Redis 8.10 or newer.
|
||||
*
|
||||
* Long-lived HIMPORT fieldsets managed for the lifetime of this client.
|
||||
* Definitions are copied during construction and prepared again whenever
|
||||
* the physical Redis connection changes.
|
||||
*
|
||||
* When a managed `HIMPORT SET` needs fieldset preparation or recovery,
|
||||
* later commands issued on this client may be sent before that SET resumes.
|
||||
* Await the SET before issuing commands that depend on its write.
|
||||
*
|
||||
* Explicit pipelines containing a managed `HIMPORT SET` wait for required
|
||||
* fieldset preparation before the batch is sent.
|
||||
*
|
||||
* Background preparation failures do not prevent the connection from
|
||||
* becoming ready and are reported through the `error` event. A dependent
|
||||
* managed `HIMPORT SET` retries preparation and rejects if recovery fails.
|
||||
*
|
||||
* Use explicit `HIMPORT PREPARE` and `DISCARD` commands on a separate
|
||||
* client for bounded, manually managed batches.
|
||||
*
|
||||
* @default undefined
|
||||
* @experimental
|
||||
*/
|
||||
himportFieldsets?: readonly HimportFieldset[] | undefined;
|
||||
}
|
||||
export type RedisOptions = CommonRedisOptions & SentinelConnectionOptions & StandaloneConnectionOptions;
|
||||
export declare const DEFAULT_REDIS_OPTIONS: RedisOptions;
|
||||
63
node_modules/ioredis/built/redis/RedisOptions.js
generated
vendored
Normal file
63
node_modules/ioredis/built/redis/RedisOptions.js
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DEFAULT_REDIS_OPTIONS = void 0;
|
||||
exports.DEFAULT_REDIS_OPTIONS = {
|
||||
// Connection
|
||||
port: 6379,
|
||||
host: "localhost",
|
||||
family: 0,
|
||||
connectTimeout: 10000,
|
||||
disconnectTimeout: 2000,
|
||||
retryStrategy: function (times) {
|
||||
const jitter = Math.floor(Math.random() * 200);
|
||||
// `times` is one-based, so the first retry uses an exponent of zero.
|
||||
const delay = Math.min(Math.pow(2, times - 1) * 50, 5000);
|
||||
return delay + jitter;
|
||||
},
|
||||
keepAlive: 30000,
|
||||
noDelay: true,
|
||||
connectionName: null,
|
||||
disableClientInfo: false,
|
||||
clientInfoTag: undefined,
|
||||
// Sentinel
|
||||
sentinels: null,
|
||||
name: null,
|
||||
role: "master",
|
||||
sentinelRetryStrategy: function (times) {
|
||||
return Math.min(times * 10, 1000);
|
||||
},
|
||||
sentinelReconnectStrategy: function () {
|
||||
// This strategy only applies when sentinels are used for detecting
|
||||
// a failover, not during initial master resolution.
|
||||
// The deployment can still function when some of the sentinels are down
|
||||
// for a long period of time, so we may not want to attempt reconnection
|
||||
// very often. Therefore the default interval is fairly long (1 minute).
|
||||
return 60000;
|
||||
},
|
||||
natMap: null,
|
||||
enableTLSForSentinelMode: false,
|
||||
updateSentinels: true,
|
||||
failoverDetector: false,
|
||||
// Status
|
||||
username: null,
|
||||
password: null,
|
||||
db: 0,
|
||||
// Others
|
||||
enableOfflineQueue: true,
|
||||
enableReadyCheck: true,
|
||||
autoResubscribe: true,
|
||||
autoResendUnfulfilledCommands: true,
|
||||
lazyConnect: false,
|
||||
keyPrefix: "",
|
||||
reconnectOnError: null,
|
||||
readOnly: false,
|
||||
stringNumbers: false,
|
||||
protocol: 3,
|
||||
replyMapping: "legacy",
|
||||
maxRetriesPerRequest: 20,
|
||||
maxLoadingRetryTime: 10000,
|
||||
enableAutoPipelining: false,
|
||||
autoPipeliningIgnoredCommands: [],
|
||||
sentinelMaxConnections: 10,
|
||||
blockingTimeoutGrace: 100,
|
||||
};
|
||||
4
node_modules/ioredis/built/redis/event_handler.d.ts
generated
vendored
Normal file
4
node_modules/ioredis/built/redis/event_handler.d.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export declare function connectHandler(self: any): () => Promise<any>;
|
||||
export declare function closeHandler(self: any): () => void;
|
||||
export declare function errorHandler(self: any): (error: any) => void;
|
||||
export declare function readyHandler(self: any): () => void;
|
||||
450
node_modules/ioredis/built/redis/event_handler.js
generated
vendored
Normal file
450
node_modules/ioredis/built/redis/event_handler.js
generated
vendored
Normal file
@@ -0,0 +1,450 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.readyHandler = exports.errorHandler = exports.closeHandler = exports.connectHandler = void 0;
|
||||
const redis_errors_1 = require("redis-errors");
|
||||
const Command_1 = require("../Command");
|
||||
const errors_1 = require("../errors");
|
||||
const utils_1 = require("../utils");
|
||||
const version_1 = require("../utils/version");
|
||||
const DataHandler_1 = require("../DataHandler");
|
||||
const HimportCoordinator_1 = require("../himport/HimportCoordinator");
|
||||
const debug = (0, utils_1.Debug)("connection");
|
||||
function getHandshakeCommands(self) {
|
||||
const commands = [];
|
||||
if (self.condition.protocol === 3) {
|
||||
const helloCommandArgs = [self.condition.protocol];
|
||||
if (self.condition.auth) {
|
||||
helloCommandArgs.push("AUTH");
|
||||
if (Array.isArray(self.condition.auth)) {
|
||||
helloCommandArgs.push(self.condition.auth[0], self.condition.auth[1]);
|
||||
}
|
||||
else {
|
||||
helloCommandArgs.push("default", self.condition.auth);
|
||||
}
|
||||
}
|
||||
commands.push({
|
||||
kind: "hello",
|
||||
send: () => self.hello(helloCommandArgs),
|
||||
errorHandler: handleAuthError,
|
||||
});
|
||||
}
|
||||
else if (self.condition.auth) {
|
||||
commands.push({
|
||||
kind: "auth",
|
||||
send: () => self.auth(self.condition.auth),
|
||||
errorHandler: handleAuthError,
|
||||
});
|
||||
}
|
||||
if (self.condition.select) {
|
||||
commands.push({
|
||||
kind: "select",
|
||||
send: () => self.select(self.condition.select),
|
||||
errorHandler: (err) => self.silentEmit("error", err),
|
||||
});
|
||||
}
|
||||
if (self.options.connectionName) {
|
||||
debug("set the connection name [%s]", self.options.connectionName);
|
||||
commands.push({
|
||||
kind: "client",
|
||||
send: () => self.client("setname", self.options.connectionName),
|
||||
errorHandler: utils_1.noop,
|
||||
});
|
||||
}
|
||||
if (self.options.readOnly) {
|
||||
debug("set the connection to readonly mode");
|
||||
commands.push({
|
||||
kind: "readonly",
|
||||
send: () => self.readonly(),
|
||||
errorHandler: utils_1.noop,
|
||||
});
|
||||
}
|
||||
if (!self.options.disableClientInfo) {
|
||||
debug("set the client info");
|
||||
commands.push({
|
||||
kind: "client",
|
||||
send: () => self.client("SETINFO", "LIB-VER", version_1.PACKAGE_VERSION),
|
||||
errorHandler: utils_1.noop,
|
||||
});
|
||||
commands.push({
|
||||
kind: "client",
|
||||
send: () => self.client("SETINFO", "LIB-NAME", self.options?.clientInfoTag
|
||||
? `ioredis(${self.options.clientInfoTag})`
|
||||
: "ioredis"),
|
||||
errorHandler: utils_1.noop,
|
||||
});
|
||||
}
|
||||
const himportBinding = (0, HimportCoordinator_1.getHimportBinding)(self);
|
||||
if (himportBinding && himportBinding.role !== "replica") {
|
||||
for (const definition of himportBinding.coordinator.getDefinitions()) {
|
||||
commands.push({
|
||||
kind: "himport",
|
||||
send: () => himportBinding.coordinator.ensurePrepared(self, definition) ??
|
||||
Promise.resolve(),
|
||||
errorHandler: (err) => self.silentEmit("error", err),
|
||||
});
|
||||
}
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
async function sendHandshake(commands, protocol) {
|
||||
if (protocol !== 3) {
|
||||
await Promise.all(commands.map(({ send, errorHandler }) => errorHandler ? send().catch(errorHandler) : send()));
|
||||
return;
|
||||
}
|
||||
// HELLO may carry AUTH. Send everything optimistically, then inspect
|
||||
// HELLO before surfacing setup errors.
|
||||
const results = await Promise.allSettled(commands.map(({ send }) => send()));
|
||||
const helloIndex = commands.findIndex(({ kind }) => kind === "hello");
|
||||
const helloResult = helloIndex === -1 ? undefined : results[helloIndex];
|
||||
if (helloResult?.status === "rejected" &&
|
||||
isProtocolNegotiationError(helloResult.reason)) {
|
||||
throw helloResult.reason;
|
||||
}
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const result = results[i];
|
||||
if (result.status === "rejected") {
|
||||
const { errorHandler } = commands[i];
|
||||
if (errorHandler) {
|
||||
errorHandler(result.reason);
|
||||
continue;
|
||||
}
|
||||
throw result.reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
function connectHandler(self) {
|
||||
return async function () {
|
||||
try {
|
||||
self.resetCommandQueue();
|
||||
self.condition.handshake = true;
|
||||
self.setStatus("connect");
|
||||
/*
|
||||
No need to keep the reference of DataHandler here
|
||||
because we don't need to do the cleanup.
|
||||
`Stream#end()` will remove all listeners for us.
|
||||
*/
|
||||
new DataHandler_1.default(self, {
|
||||
stringNumbers: self.options.stringNumbers,
|
||||
replyMapping: self.condition.replyMapping,
|
||||
});
|
||||
const { connectionEpoch } = self;
|
||||
const himportBinding = (0, HimportCoordinator_1.getHimportBinding)(self);
|
||||
himportBinding?.coordinator.beginSession(self);
|
||||
const isActiveConnect = () => connectionEpoch === self.connectionEpoch && self.status === "connect";
|
||||
const finishHandshake = () => {
|
||||
if (!isActiveConnect()) {
|
||||
return false;
|
||||
}
|
||||
self.condition.handshake = false;
|
||||
return true;
|
||||
};
|
||||
try {
|
||||
await sendHandshake(getHandshakeCommands(self), self.condition.protocol);
|
||||
}
|
||||
catch (err) {
|
||||
// The connection may have been closed (and possibly already
|
||||
// reconnected) while the client setup commands above were still
|
||||
// in flight. In that case this callback is stale and the
|
||||
// connection is no longer the one we are setting up, so calling
|
||||
// readyHandler() would mark a dead connection as "ready" and
|
||||
// permanently stop reconnection. The enableReadyCheck branch
|
||||
// below is already guarded by the same connectionEpoch check.
|
||||
// See https://github.com/redis/ioredis/issues/2099
|
||||
if (!isActiveConnect()) {
|
||||
return;
|
||||
}
|
||||
// Only an unsupported-RESP3 error is recoverable; everything else is fatal.
|
||||
if (!isProtocolNegotiationError(err)) {
|
||||
return self.recoverFromFatalError(err, err);
|
||||
}
|
||||
// resp3 shapes are unavailable on RESP2, but the data is unaffected
|
||||
// (RESP2 never sends the remapped frames), so just warn — don't touch
|
||||
// the option.
|
||||
if (self.options.replyMapping === "resp3") {
|
||||
console.warn('[WARN] replyMapping "resp3" was requested, but the server does not support RESP3. ' +
|
||||
"Replies will use RESP2-compatible shapes until connected to a server that supports RESP3.");
|
||||
}
|
||||
debug("server rejected RESP3, downgrading connection to RESP2");
|
||||
self.condition.protocol = 2;
|
||||
self.condition.replyMapping = "legacy";
|
||||
try {
|
||||
await sendHandshake(getHandshakeCommands(self), self.condition.protocol);
|
||||
}
|
||||
catch (downgradeErr) {
|
||||
if (!isActiveConnect()) {
|
||||
return;
|
||||
}
|
||||
return self.recoverFromFatalError(downgradeErr, downgradeErr);
|
||||
}
|
||||
}
|
||||
if (!isActiveConnect()) {
|
||||
return;
|
||||
}
|
||||
// Keep the public "connect" state/event while the internal handshake gate
|
||||
// stays closed. INFO is a handshake command so it's still writable here,
|
||||
// while a user SUBSCRIBE is not — it stays queued and can't race the
|
||||
// connection into subscriber mode before we're ready (which would get
|
||||
// INFO rejected).
|
||||
if (!self.options.enableReadyCheck) {
|
||||
if (!finishHandshake()) {
|
||||
return;
|
||||
}
|
||||
return exports.readyHandler(self)();
|
||||
}
|
||||
self._readyCheck(function (err, info) {
|
||||
if (!isActiveConnect()) {
|
||||
return;
|
||||
}
|
||||
if (err) {
|
||||
self.recoverFromFatalError(err, err);
|
||||
}
|
||||
else if (self.connector.check(info)) {
|
||||
if (!finishHandshake()) {
|
||||
return;
|
||||
}
|
||||
exports.readyHandler(self)();
|
||||
}
|
||||
else {
|
||||
self.disconnect(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
self.recoverFromFatalError(err, err);
|
||||
}
|
||||
};
|
||||
}
|
||||
exports.connectHandler = connectHandler;
|
||||
function handleAuthError(err) {
|
||||
const msg = err.message || "";
|
||||
if (msg.indexOf("no password is set") !== -1) {
|
||||
console.warn("[WARN] Redis server does not require a password, but a password was supplied.");
|
||||
return;
|
||||
}
|
||||
if (msg.indexOf("without any password configured for the default user") !== -1) {
|
||||
console.warn("[WARN] This Redis server's `default` user does not require a password, but a password was supplied");
|
||||
return;
|
||||
}
|
||||
if (msg.indexOf("wrong number of arguments for 'auth' command") !== -1) {
|
||||
console.warn(`[ERROR] The server returned "wrong number of arguments for 'auth' command". You are probably passing both username and password to Redis version 5 or below. You should only pass the 'password' option for Redis version 5 and under.`);
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
// True when HELLO 3 failed because the server can't do RESP3: no HELLO at all
|
||||
// (Redis < 6) or HELLO present but the version refused (NOPROTO).
|
||||
function isProtocolNegotiationError(err) {
|
||||
const msg = (err.message || "").toUpperCase();
|
||||
return (msg.includes("NOPROTO") ||
|
||||
(msg.includes("UNKNOWN COMMAND") && msg.includes("HELLO")));
|
||||
}
|
||||
function abortError(command) {
|
||||
const err = new redis_errors_1.AbortError("Command aborted due to connection close");
|
||||
err.command = {
|
||||
name: command.name,
|
||||
args: command.args,
|
||||
};
|
||||
return err;
|
||||
}
|
||||
// If a contiguous set of pipeline commands starts from index zero then they
|
||||
// can be safely reattempted. If however we have a chain of pipelined commands
|
||||
// starting at index 1 or more it means we received a partial response before
|
||||
// the connection close and those pipelined commands must be aborted. For
|
||||
// example, if the queue looks like this: [2, 3, 4, 0, 1, 2] then after
|
||||
// aborting and purging we'll have a queue that looks like this: [0, 1, 2]
|
||||
function abortIncompletePipelines(commandQueue) {
|
||||
let expectedIndex = 0;
|
||||
for (let i = 0; i < commandQueue.length;) {
|
||||
const command = commandQueue.peekAt(i)?.command;
|
||||
const pipelineIndex = command.pipelineIndex;
|
||||
if (pipelineIndex === undefined || pipelineIndex === 0) {
|
||||
expectedIndex = 0;
|
||||
}
|
||||
if (pipelineIndex !== undefined && pipelineIndex !== expectedIndex++) {
|
||||
commandQueue.remove(i, 1);
|
||||
command.reject(abortError(command));
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// If only a partial transaction result was received before connection close,
|
||||
// we have to abort any transaction fragments that may have ended up in the
|
||||
// offline queue
|
||||
function abortTransactionFragments(commandQueue) {
|
||||
for (let i = 0; i < commandQueue.length;) {
|
||||
const command = commandQueue.peekAt(i)?.command;
|
||||
if (command.name === "multi") {
|
||||
break;
|
||||
}
|
||||
if (command.name === "exec") {
|
||||
commandQueue.remove(i, 1);
|
||||
command.reject(abortError(command));
|
||||
break;
|
||||
}
|
||||
if (command.inTransaction) {
|
||||
commandQueue.remove(i, 1);
|
||||
command.reject(abortError(command));
|
||||
}
|
||||
else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
function closeHandler(self) {
|
||||
return function () {
|
||||
const prevStatus = self.status;
|
||||
self.setStatus("close");
|
||||
// The timeout belongs to the stream that just closed and must not survive reconnect.
|
||||
if (self.socketTimeoutTimer !== undefined) {
|
||||
clearTimeout(self.socketTimeoutTimer);
|
||||
self.socketTimeoutTimer = undefined;
|
||||
}
|
||||
if (self.commandQueue.length) {
|
||||
abortIncompletePipelines(self.commandQueue);
|
||||
}
|
||||
if (self.offlineQueue.length) {
|
||||
abortTransactionFragments(self.offlineQueue);
|
||||
}
|
||||
if (prevStatus === "ready") {
|
||||
if (!self.prevCondition) {
|
||||
self.prevCondition = self.condition;
|
||||
}
|
||||
if (self.commandQueue.length) {
|
||||
self.prevCommandQueue = self.commandQueue;
|
||||
}
|
||||
}
|
||||
if (self.manuallyClosing) {
|
||||
self.manuallyClosing = false;
|
||||
debug("skip reconnecting since the connection is manually closed.");
|
||||
return close();
|
||||
}
|
||||
if (typeof self.options.retryStrategy !== "function") {
|
||||
debug("skip reconnecting because `retryStrategy` is not a function");
|
||||
return close();
|
||||
}
|
||||
const retryDelay = self.options.retryStrategy(++self.retryAttempts);
|
||||
if (typeof retryDelay !== "number") {
|
||||
debug("skip reconnecting because `retryStrategy` doesn't return a number");
|
||||
return close();
|
||||
}
|
||||
debug("reconnect in %sms", retryDelay);
|
||||
self.setStatus("reconnecting", retryDelay);
|
||||
self.reconnectTimeout = setTimeout(function () {
|
||||
self.reconnectTimeout = null;
|
||||
self.connect().catch(utils_1.noop);
|
||||
}, retryDelay);
|
||||
const { maxRetriesPerRequest } = self.options;
|
||||
if (typeof maxRetriesPerRequest === "number") {
|
||||
if (maxRetriesPerRequest < 0) {
|
||||
debug("maxRetriesPerRequest is negative, ignoring...");
|
||||
}
|
||||
else {
|
||||
const remainder = self.retryAttempts % (maxRetriesPerRequest + 1);
|
||||
if (remainder === 0) {
|
||||
debug("reach maxRetriesPerRequest limitation, flushing command queue...");
|
||||
self.flushQueue(new errors_1.MaxRetriesPerRequestError(maxRetriesPerRequest));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
function close() {
|
||||
self.setStatus("end");
|
||||
self.flushQueue(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
|
||||
}
|
||||
}
|
||||
exports.closeHandler = closeHandler;
|
||||
function errorHandler(self) {
|
||||
return function (error) {
|
||||
debug("error: %s", error);
|
||||
self.silentEmit("error", error);
|
||||
};
|
||||
}
|
||||
exports.errorHandler = errorHandler;
|
||||
function readyHandler(self) {
|
||||
return function () {
|
||||
self.setStatus("ready");
|
||||
self.retryAttempts = 0;
|
||||
if (self.options.monitor) {
|
||||
self.call("monitor").then(() => self.setStatus("monitoring"), (error) => self.emit("error", error));
|
||||
const { sendCommand } = self;
|
||||
self.sendCommand = function (command) {
|
||||
if (Command_1.default.checkFlag("VALID_IN_MONITOR_MODE", command.name)) {
|
||||
return sendCommand.call(self, command);
|
||||
}
|
||||
command.reject(new Error("Connection is in monitoring mode, can't process commands."));
|
||||
return command.promise;
|
||||
};
|
||||
self.once("close", function () {
|
||||
delete self.sendCommand;
|
||||
});
|
||||
return;
|
||||
}
|
||||
const finalSelect = self.prevCondition
|
||||
? self.prevCondition.select
|
||||
: self.condition.select;
|
||||
if (self.prevCondition) {
|
||||
const condition = self.prevCondition;
|
||||
self.prevCondition = null;
|
||||
if (condition.subscriber && self.options.autoResubscribe) {
|
||||
// We re-select the previous db first since
|
||||
// `SELECT` command is not valid in sub mode.
|
||||
if (self.condition.select !== finalSelect) {
|
||||
debug("connect to db [%d]", finalSelect);
|
||||
self.select(finalSelect);
|
||||
}
|
||||
const subscribeChannels = condition.subscriber.channels("subscribe");
|
||||
if (subscribeChannels.length) {
|
||||
debug("subscribe %d channels", subscribeChannels.length);
|
||||
self.subscribe(subscribeChannels);
|
||||
}
|
||||
const psubscribeChannels = condition.subscriber.channels("psubscribe");
|
||||
if (psubscribeChannels.length) {
|
||||
debug("psubscribe %d channels", psubscribeChannels.length);
|
||||
self.psubscribe(psubscribeChannels);
|
||||
}
|
||||
const ssubscribeChannels = condition.subscriber.channels("ssubscribe");
|
||||
if (ssubscribeChannels.length) {
|
||||
debug("ssubscribe %s", ssubscribeChannels.length);
|
||||
for (const channel of ssubscribeChannels) {
|
||||
self.ssubscribe(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self.prevCommandQueue) {
|
||||
if (self.options.autoResendUnfulfilledCommands) {
|
||||
debug("resend %d unfulfilled commands", self.prevCommandQueue.length);
|
||||
while (self.prevCommandQueue.length > 0) {
|
||||
const item = self.prevCommandQueue.shift();
|
||||
if (item.select !== self.condition.select &&
|
||||
item.command.name !== "select") {
|
||||
self.select(item.select);
|
||||
}
|
||||
self.sendCommand(item.command, item.stream);
|
||||
}
|
||||
}
|
||||
else {
|
||||
self.prevCommandQueue = null;
|
||||
}
|
||||
}
|
||||
if (self.offlineQueue.length) {
|
||||
debug("send %d commands in offline queue", self.offlineQueue.length);
|
||||
const offlineQueue = self.offlineQueue;
|
||||
self.resetOfflineQueue();
|
||||
while (offlineQueue.length > 0) {
|
||||
const item = offlineQueue.shift();
|
||||
if (item.select !== self.condition.select &&
|
||||
item.command.name !== "select") {
|
||||
self.select(item.select);
|
||||
}
|
||||
self.sendCommand(item.command, item.stream);
|
||||
}
|
||||
}
|
||||
if (self.condition.select !== finalSelect) {
|
||||
debug("connect to db [%d]", finalSelect);
|
||||
self.select(finalSelect);
|
||||
}
|
||||
};
|
||||
}
|
||||
exports.readyHandler = readyHandler;
|
||||
Reference in New Issue
Block a user