Review funkt

This commit is contained in:
2026-08-09 21:56:00 +00:00
parent f522ae9953
commit fe9dfa4519
11366 changed files with 2335077 additions and 97 deletions

227
node_modules/ioredis/built/cluster/ClusterOptions.d.ts generated vendored Normal file
View File

@@ -0,0 +1,227 @@
/// <reference types="node" />
/// <reference types="node" />
import { SrvRecord } from "dns";
import { RedisOptions, RetryStrategy } from "../redis/RedisOptions";
import { ReplyMappingMode } from "../types";
import { CommanderOptions } from "../utils/Commander";
import { NodeRole } from "./util";
import type { HimportFieldset } from "../himport/types";
export type DNSResolveSrvFunction = (hostname: string, callback: (err: NodeJS.ErrnoException | null | undefined, records?: SrvRecord[]) => void) => void;
export type DNSLookupFunction = (hostname: string, callback: (err: NodeJS.ErrnoException | null | undefined, address: string, family?: number) => void) => void;
export type NatMapFunction = (key: string) => {
host: string;
port: number;
} | null;
export type NatMap = {
[key: string]: {
host: string;
port: number;
};
} | NatMapFunction;
export type ClusterNodeRetryStrategy = RetryStrategy;
/**
* Options for Cluster constructor
*/
export interface ClusterOptions extends CommanderOptions {
/**
* See "Quick Start" section.
*
* @default (times) => Math.min(100 + times * 2, 2000)
*/
clusterRetryStrategy?: ((times: number, reason?: Error) => number | void | null) | null | undefined;
/**
* See Redis class.
*
* @default true
*/
enableOfflineQueue?: boolean | undefined;
/**
* When enabled, ioredis only emits "ready" event when `CLUSTER INFO`
* command reporting the cluster is ready for handling commands.
*
* @default true
*/
enableReadyCheck?: boolean | undefined;
/**
* Scale reads to the node with the specified role.
*
* @default "master"
*/
scaleReads?: NodeRole | Function | undefined;
/**
* When a MOVED or ASK error is received, client will redirect the
* command to another node.
* This option limits the max redirections allowed to send a command.
*
* @default 16
*/
maxRedirections?: number | undefined;
/**
* When an error is received when sending a command (e.g.
* "Connection is closed." when the target Redis node is down), client will retry
* if `retryDelayOnFailover` is valid delay time (in ms).
*
* @default 100
*/
retryDelayOnFailover?: number | undefined;
/**
* When a CLUSTERDOWN error is received, client will retry
* if `retryDelayOnClusterDown` is valid delay time (in ms).
*
* @default 100
*/
retryDelayOnClusterDown?: number | undefined;
/**
* When a TRYAGAIN error is received, client will retry
* if `retryDelayOnTryAgain` is valid delay time (in ms).
*
* @default 100
*/
retryDelayOnTryAgain?: number | undefined;
/**
* By default, this value is 0, which means when a `MOVED` error is received,
* the client will resend the command instantly to the node returned together with
* the `MOVED` error. However, sometimes it takes time for a cluster to become
* state stabilized after a failover, so adding a delay before resending can
* prevent a ping pong effect.
*
* @default 0
*/
retryDelayOnMoved?: number | undefined;
/**
* The milliseconds before a timeout occurs while refreshing
* slots from the cluster.
*
* @default 1000
*/
slotsRefreshTimeout?: number | undefined;
/**
* The milliseconds between every automatic slots refresh.
*
* @default 5000
*/
slotsRefreshInterval?: number | undefined;
/**
* Use sharded subscribers instead of a single subscriber.
*
* If sharded subscribers are used, then one additional subscriber connection per master node
* is established. If you don't plan to use SPUBLISH/SSUBSCRIBE, then this should be disabled.
*
* @default false
*/
shardedSubscribers?: boolean | undefined;
/**
* When a cluster node connection is closed, this function will be called
* to determine the retry delay (in ms). Returning `null` or a non-number
* disables reconnection for that node.
*
* By default this is `null`, meaning cluster nodes will NOT automatically
* reconnect — the cluster relies on `MOVED` errors to refresh topology.
* Set this to enable reconnection, e.g. for replica nodes that restart
* without any slot changes.
*
* @example
* clusterNodeRetryStrategy: (times) => Math.min(times * 100, 3000)
*
* @default null
*/
clusterNodeRetryStrategy?: ClusterNodeRetryStrategy;
/**
* Passed to the constructor of `Redis`
*
* @default null
*/
redisOptions?: Omit<RedisOptions, "port" | "host" | "path" | "sentinels" | "retryStrategy" | "enableOfflineQueue" | "readOnly" | "himportFieldsets"> | undefined;
/**
* By default, When a new Cluster instance is created,
* it will connect to the Redis cluster automatically.
* If you want to keep the instance disconnected until the first command is called,
* set this option to `true`.
*
* @default false
*/
lazyConnect?: boolean | undefined;
/**
* Discover nodes using SRV records
*
* @default false
*/
useSRVRecords?: boolean | undefined;
/**
* SRV records will be resolved via this function.
*
* You may provide a custom `resolveSrv` function when you want to customize
* the cache behavior of the default function.
*
* @default require('dns').resolveSrv
*/
resolveSrv?: DNSResolveSrvFunction | undefined;
/**
* Hostnames will be resolved to IP addresses via this function.
* This is needed when the addresses of startup nodes are hostnames instead
* of IPs.
*
* You may provide a custom `lookup` function when you want to customize
* the cache behavior of the default function.
*
* @default require('dns').lookup
*/
dnsLookup?: DNSLookupFunction | undefined;
natMap?: NatMap | undefined;
/**
* See Redis class.
*
* @default false
*/
enableAutoPipelining?: boolean | undefined;
/**
* See Redis class.
*
* @default []
*/
autoPipeliningIgnoredCommands?: string[] | undefined;
/**
* Custom LUA commands
*/
scripts?: Record<string, {
lua: string;
numberOfKeys?: number;
readOnly?: boolean;
}> | undefined;
/**
* Managed-fieldset support is experimental and requires Redis 8.10 or newer.
*
* Long-lived HIMPORT fieldsets managed across all current and future master
* connections for the lifetime of this Cluster client.
* Configure this option at the top level, not under `redisOptions`.
*
* When a managed `HIMPORT SET` needs fieldset preparation or recovery,
* later commands issued on this Cluster 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 on the selected master before the batch is sent.
*
* Background preparation failures do not prevent the connection from
* becoming ready and are reported through the `node error` event. A
* dependent managed `HIMPORT SET` retries preparation and rejects if
* recovery fails.
*
* Direct `HIMPORT PREPARE`, `DISCARD`, and `DISCARDALL` calls fan out to all
* current masters. Within an explicit pipeline, these commands remain
* connection-affine and are not managed.
*
* Use explicit HIMPORT commands on a separate unconfigured client for
* bounded, manually managed batches.
*
* @default undefined
* @experimental
*/
himportFieldsets?: readonly HimportFieldset[] | undefined;
}
export type ClusterOptionsWithReplyMapping<Mapping extends ReplyMappingMode> = ClusterOptions & {
redisOptions?: ClusterOptions["redisOptions"] & {
replyMapping?: Mapping;
};
};
export declare const DEFAULT_CLUSTER_OPTIONS: ClusterOptions;

23
node_modules/ioredis/built/cluster/ClusterOptions.js generated vendored Normal file
View File

@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_CLUSTER_OPTIONS = void 0;
const dns_1 = require("dns");
exports.DEFAULT_CLUSTER_OPTIONS = {
clusterRetryStrategy: (times) => Math.min(100 + times * 2, 2000),
clusterNodeRetryStrategy: null,
enableOfflineQueue: true,
enableReadyCheck: true,
scaleReads: "master",
maxRedirections: 16,
retryDelayOnMoved: 0,
retryDelayOnFailover: 100,
retryDelayOnClusterDown: 100,
retryDelayOnTryAgain: 100,
slotsRefreshTimeout: 1000,
useSRVRecords: false,
resolveSrv: dns_1.resolveSrv,
dnsLookup: dns_1.lookup,
enableAutoPipelining: false,
autoPipeliningIgnoredCommands: [],
shardedSubscribers: false,
};

View File

@@ -0,0 +1,29 @@
/// <reference types="node" />
import { EventEmitter } from "events";
import ConnectionPool from "./ConnectionPool";
export default class ClusterSubscriber {
private connectionPool;
private emitter;
private isSharded;
private started;
private subscriber;
private lastActiveSubscriber;
private slotRange;
constructor(connectionPool: ConnectionPool, emitter: EventEmitter, isSharded?: boolean);
getInstance(): any;
/**
* Associate this subscriber to a specific slot range.
*
* Returns the range or an empty array if the slot range couldn't be associated.
*
* BTW: This is more for debugging and testing purposes.
*
* @param range
*/
associateSlotRange(range: number[]): number[];
start(): void;
stop(): void;
isStarted(): boolean;
private onSubscriberEnd;
private selectSubscriber;
}

225
node_modules/ioredis/built/cluster/ClusterSubscriber.js generated vendored Normal file
View File

@@ -0,0 +1,225 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("./util");
const utils_1 = require("../utils");
const Redis_1 = require("../Redis");
const debug = (0, utils_1.Debug)("cluster:subscriber");
class ClusterSubscriber {
constructor(connectionPool, emitter, isSharded = false) {
this.connectionPool = connectionPool;
this.emitter = emitter;
this.isSharded = isSharded;
this.started = false;
//There is only one connection for the entire pool
this.subscriber = null;
//The slot range for which this subscriber is responsible
this.slotRange = [];
this.onSubscriberEnd = () => {
if (!this.started) {
debug("subscriber has disconnected, but ClusterSubscriber is not started, so not reconnecting.");
return;
}
// If the subscriber closes whilst it's still the active connection,
// we might as well try to connecting to a new node if possible to
// minimise the number of missed publishes.
debug("subscriber has disconnected, selecting a new one...");
this.selectSubscriber();
};
// If the current node we're using as the subscriber disappears
// from the node pool for some reason, we will select a new one
// to connect to.
// Note that this event is only triggered if the connection to
// the node has been used; cluster subscriptions are setup with
// lazyConnect = true. It's possible for the subscriber node to
// disappear without this method being called!
// See https://github.com/luin/ioredis/pull/1589
this.connectionPool.on("-node", (_, key) => {
if (!this.started || !this.subscriber) {
return;
}
if ((0, util_1.getNodeKey)(this.subscriber.options) === key) {
debug("subscriber has left, selecting a new one...");
this.selectSubscriber();
}
});
this.connectionPool.on("+node", () => {
if (!this.started || this.subscriber) {
return;
}
debug("a new node is discovered and there is no subscriber, selecting a new one...");
this.selectSubscriber();
});
}
getInstance() {
return this.subscriber;
}
/**
* Associate this subscriber to a specific slot range.
*
* Returns the range or an empty array if the slot range couldn't be associated.
*
* BTW: This is more for debugging and testing purposes.
*
* @param range
*/
associateSlotRange(range) {
if (this.isSharded) {
this.slotRange = range;
}
return this.slotRange;
}
start() {
this.started = true;
this.selectSubscriber();
debug("started");
}
stop() {
this.started = false;
if (this.subscriber) {
this.subscriber.disconnect();
this.subscriber = null;
}
}
isStarted() {
return this.started;
}
selectSubscriber() {
const lastActiveSubscriber = this.lastActiveSubscriber;
// Disconnect the previous subscriber even if there
// will not be a new one.
if (lastActiveSubscriber) {
lastActiveSubscriber.off("end", this.onSubscriberEnd);
lastActiveSubscriber.disconnect();
}
if (this.subscriber) {
this.subscriber.off("end", this.onSubscriberEnd);
this.subscriber.disconnect();
}
const sampleNode = (0, utils_1.sample)(this.connectionPool.getNodes());
if (!sampleNode) {
debug("selecting subscriber failed since there is no node discovered in the cluster yet");
this.subscriber = null;
return;
}
const { options } = sampleNode;
debug("selected a subscriber %s:%s", options.host, options.port);
/*
* Create a specialized Redis connection for the subscription.
* Note that auto reconnection is enabled here.
*
* `enableReadyCheck` is also enabled because although subscription is allowed
* while redis is loading data from the disk, we can check if the password
* provided for the subscriber is correct, and if not, the current subscriber
* will be disconnected and a new subscriber will be selected.
*/
let connectionPrefix = "subscriber";
if (this.isSharded)
connectionPrefix = "ssubscriber";
this.subscriber = new Redis_1.default({
port: options.port,
host: options.host,
username: options.username,
password: options.password,
enableReadyCheck: true,
connectionName: (0, util_1.getConnectionName)(connectionPrefix, options.connectionName),
lazyConnect: true,
tls: options.tls,
protocol: options.protocol,
replyMapping: options.replyMapping,
// Don't try to reconnect the subscriber connection. If the connection fails
// we will get an end event (handled below), at which point we'll pick a new
// node from the pool and try to connect to that as the subscriber connection.
retryStrategy: null,
});
// Ignore the errors since they're handled in the connection pool.
this.subscriber.on("error", utils_1.noop);
this.subscriber.on("moved", () => {
this.emitter.emit("forceRefresh");
});
// The node we lost connection to may not come back up in a
// reasonable amount of time (e.g. a slave that's taken down
// for maintainence), we could potentially miss many published
// messages so we should reconnect as quickly as possible, to
// a different node if needed.
this.subscriber.once("end", this.onSubscriberEnd);
// Re-subscribe previous channels
const previousChannels = { subscribe: [], psubscribe: [], ssubscribe: [] };
if (lastActiveSubscriber) {
const condition = lastActiveSubscriber.condition || lastActiveSubscriber.prevCondition;
if (condition && condition.subscriber) {
previousChannels.subscribe = condition.subscriber.channels("subscribe");
previousChannels.psubscribe =
condition.subscriber.channels("psubscribe");
previousChannels.ssubscribe =
condition.subscriber.channels("ssubscribe");
}
}
if (previousChannels.subscribe.length ||
previousChannels.psubscribe.length ||
previousChannels.ssubscribe.length) {
let pending = 0;
for (const type of ["subscribe", "psubscribe", "ssubscribe"]) {
const channels = previousChannels[type];
if (channels.length == 0) {
continue;
}
debug("%s %d channels", type, channels.length);
if (type === "ssubscribe") {
for (const channel of channels) {
pending += 1;
this.subscriber[type](channel)
.then(() => {
if (!--pending) {
this.lastActiveSubscriber = this.subscriber;
}
})
.catch(() => {
// TODO: should probably disconnect the subscriber and try again.
debug("failed to ssubscribe to channel: %s", channel);
});
}
}
else {
pending += 1;
this.subscriber[type](channels)
.then(() => {
if (!--pending) {
this.lastActiveSubscriber = this.subscriber;
}
})
.catch(() => {
// TODO: should probably disconnect the subscriber and try again.
debug("failed to %s %d channels", type, channels.length);
});
}
}
}
else {
this.lastActiveSubscriber = this.subscriber;
}
for (const event of [
"message",
"messageBuffer",
]) {
this.subscriber.on(event, (arg1, arg2) => {
this.emitter.emit(event, arg1, arg2);
});
}
for (const event of ["pmessage", "pmessageBuffer"]) {
this.subscriber.on(event, (arg1, arg2, arg3) => {
this.emitter.emit(event, arg1, arg2, arg3);
});
}
if (this.isSharded == true) {
for (const event of [
"smessage",
"smessageBuffer",
]) {
this.subscriber.on(event, (arg1, arg2) => {
this.emitter.emit(event, arg1, arg2);
});
}
}
}
}
exports.default = ClusterSubscriber;

View File

@@ -0,0 +1,110 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import * as EventEmitter from "events";
import ShardedSubscriber from "./ShardedSubscriber";
import { ClusterOptions } from "./ClusterOptions";
/**
* Redis distinguishes between "normal" and sharded PubSub. When using the normal PubSub feature,
* exactly one subscriber exists per cluster instance because the Redis cluster bus forwards
* messages between shards. Sharded PubSub removes this limitation by making each shard
* responsible for its own messages.
*
* This class coordinates one ShardedSubscriber per master node in the cluster, providing
* sharded PubSub support while keeping the public API backward compatible.
*/
export default class ClusterSubscriberGroup {
private readonly subscriberGroupEmitter;
private readonly options;
private shardedSubscribers;
private clusterSlots;
private subscriberToSlotsIndex;
private channels;
private failedAttemptsByNode;
private isResetting;
private pendingReset;
private static readonly MAX_RETRY_ATTEMPTS;
private static readonly MAX_BACKOFF_MS;
private static readonly BASE_BACKOFF_MS;
/**
* Register callbacks
*
* @param cluster
*/
constructor(subscriberGroupEmitter: EventEmitter, options: ClusterOptions);
/**
* Get the responsible subscriber.
*
* @param slot
*/
getResponsibleSubscriber(slot: number): ShardedSubscriber | undefined;
/**
* Adds a channel for which this subscriber group is responsible
*
* @param channels
*/
addChannels(channels: (string | Buffer)[]): number;
/**
* Removes channels for which the subscriber group is responsible by optionally unsubscribing
* @param channels
*/
removeChannels(channels: (string | Buffer)[]): number;
/**
* Disconnect all subscribers and clear some of the internal state.
*/
stop(): void;
/**
* Start all not yet started subscribers
*/
start(): Promise<any[]>;
/**
* Resets the subscriber group by disconnecting all subscribers that are no longer needed and connecting new ones.
*/
reset(clusterSlots: string[][], clusterNodes: any[]): Promise<void>;
/**
* Refreshes the subscriber-related slot ranges
*
* Returns false if no refresh was needed
*
* @param targetSlots
*/
private _refreshSlots;
/**
* Resubscribes to the previous channels
*
* @private
*/
private _resubscribe;
/**
* Deep equality of the cluster slots objects
*
* @param other
* @private
*/
private _slotsAreEqual;
/**
* Checks if any subscribers are in an unhealthy state.
*
* A subscriber is considered unhealthy if:
* - It exists but is not started (failed/disconnected)
* - It's missing entirely for a node that should have one
*
* @returns true if any subscribers need to be recreated
*/
private hasUnhealthySubscribers;
/**
* Handles failed subscriber connections by emitting an event to refresh the slots cache
* after a backoff period.
*
* @param error
* @param nodeKey
*/
private handleSubscriberConnectFailed;
/**
* Handles successful subscriber connections by resetting the failed attempts counter.
*
* @param nodeKey
*/
private handleSubscriberConnectSucceeded;
private shouldStartSubscriber;
}

View File

@@ -0,0 +1,373 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const util_1 = require("./util");
const calculateSlot = require("cluster-key-slot");
const ShardedSubscriber_1 = require("./ShardedSubscriber");
const debug = (0, utils_1.Debug)("cluster:subscriberGroup");
/**
* Redis distinguishes between "normal" and sharded PubSub. When using the normal PubSub feature,
* exactly one subscriber exists per cluster instance because the Redis cluster bus forwards
* messages between shards. Sharded PubSub removes this limitation by making each shard
* responsible for its own messages.
*
* This class coordinates one ShardedSubscriber per master node in the cluster, providing
* sharded PubSub support while keeping the public API backward compatible.
*/
class ClusterSubscriberGroup {
/**
* Register callbacks
*
* @param cluster
*/
constructor(subscriberGroupEmitter, options) {
this.subscriberGroupEmitter = subscriberGroupEmitter;
this.options = options;
this.shardedSubscribers = new Map();
this.clusterSlots = [];
// Simple [min, max] slot ranges aren't enough because you can migrate single slots
this.subscriberToSlotsIndex = new Map();
this.channels = new Map();
this.failedAttemptsByNode = new Map();
// Only latest pending reset kept; throttled by refreshSlotsCache's isRefreshing + backoff delay
this.isResetting = false;
this.pendingReset = null;
/**
* Handles failed subscriber connections by emitting an event to refresh the slots cache
* after a backoff period.
*
* @param error
* @param nodeKey
*/
this.handleSubscriberConnectFailed = (error, nodeKey) => {
const currentAttempts = this.failedAttemptsByNode.get(nodeKey) || 0;
const failedAttempts = currentAttempts + 1;
this.failedAttemptsByNode.set(nodeKey, failedAttempts);
const attempts = Math.min(failedAttempts, ClusterSubscriberGroup.MAX_RETRY_ATTEMPTS);
const backoff = Math.min(ClusterSubscriberGroup.BASE_BACKOFF_MS * 2 ** attempts, ClusterSubscriberGroup.MAX_BACKOFF_MS);
const jitter = Math.floor((Math.random() - 0.5) * (backoff * 0.5));
const delay = Math.max(0, backoff + jitter);
debug("Failed to connect subscriber for %s. Refreshing slots in %dms", nodeKey, delay);
this.subscriberGroupEmitter.emit("subscriberConnectFailed", {
delay,
error,
});
};
/**
* Handles successful subscriber connections by resetting the failed attempts counter.
*
* @param nodeKey
*/
this.handleSubscriberConnectSucceeded = (nodeKey) => {
this.failedAttemptsByNode.delete(nodeKey);
};
}
/**
* Get the responsible subscriber.
*
* @param slot
*/
getResponsibleSubscriber(slot) {
const nodeKey = this.clusterSlots[slot][0];
const sub = this.shardedSubscribers.get(nodeKey);
if (sub && sub.subscriberStatus === "idle") {
sub
.start()
.then(() => {
this.handleSubscriberConnectSucceeded(sub.getNodeKey());
})
.catch((err) => {
this.handleSubscriberConnectFailed(err, sub.getNodeKey());
});
}
return sub;
}
/**
* Adds a channel for which this subscriber group is responsible
*
* @param channels
*/
addChannels(channels) {
const slot = calculateSlot(channels[0]);
// Check if the all channels belong to the same slot and otherwise reject the operation
for (const c of channels) {
if (calculateSlot(c) !== slot) {
return -1;
}
}
const currChannels = this.channels.get(slot);
if (!currChannels) {
this.channels.set(slot, channels);
}
else {
this.channels.set(slot, currChannels.concat(channels));
}
return Array.from(this.channels.values()).reduce((sum, array) => sum + array.length, 0);
}
/**
* Removes channels for which the subscriber group is responsible by optionally unsubscribing
* @param channels
*/
removeChannels(channels) {
const slot = calculateSlot(channels[0]);
// Check if the all channels belong to the same slot and otherwise reject the operation
for (const c of channels) {
if (calculateSlot(c) !== slot) {
return -1;
}
}
const slotChannels = this.channels.get(slot);
if (slotChannels) {
const updatedChannels = slotChannels.filter((c) => !channels.includes(c));
this.channels.set(slot, updatedChannels);
}
return Array.from(this.channels.values()).reduce((sum, array) => sum + array.length, 0);
}
/**
* Disconnect all subscribers and clear some of the internal state.
*/
stop() {
for (const s of this.shardedSubscribers.values()) {
s.stop();
}
// Clear subscriber instances and pending operations.
// Channels are preserved for resubscription on reconnect.
this.pendingReset = null;
this.shardedSubscribers.clear();
this.subscriberToSlotsIndex.clear();
}
/**
* Start all not yet started subscribers
*/
start() {
const startPromises = [];
for (const s of this.shardedSubscribers.values()) {
if (this.shouldStartSubscriber(s)) {
startPromises.push(s
.start()
.then(() => {
this.handleSubscriberConnectSucceeded(s.getNodeKey());
})
.catch((err) => {
this.handleSubscriberConnectFailed(err, s.getNodeKey());
}));
this.subscriberGroupEmitter.emit("+subscriber");
}
}
return Promise.all(startPromises);
}
/**
* Resets the subscriber group by disconnecting all subscribers that are no longer needed and connecting new ones.
*/
async reset(clusterSlots, clusterNodes) {
if (this.isResetting) {
this.pendingReset = { slots: clusterSlots, nodes: clusterNodes };
return;
}
this.isResetting = true;
try {
const hasTopologyChanged = this._refreshSlots(clusterSlots);
const hasFailedSubscribers = this.hasUnhealthySubscribers();
if (!hasTopologyChanged && !hasFailedSubscribers) {
debug("No topology change detected or failed subscribers. Skipping reset.");
return;
}
// For each of the sharded subscribers
for (const [nodeKey, shardedSubscriber] of this.shardedSubscribers) {
if (
// If the subscriber is still responsible for a slot range and is healthy then keep it
this.subscriberToSlotsIndex.has(nodeKey) &&
shardedSubscriber.isHealthy()) {
debug("Skipping deleting subscriber for %s", nodeKey);
continue;
}
debug("Removing subscriber for %s", nodeKey);
// Otherwise stop the subscriber and remove it
shardedSubscriber.stop();
this.shardedSubscribers.delete(nodeKey);
this.subscriberGroupEmitter.emit("-subscriber");
}
const startPromises = [];
// For each node in slots cache
for (const [nodeKey, _] of this.subscriberToSlotsIndex) {
const existingSubscriber = this.shardedSubscribers.get(nodeKey);
// If we already have a subscriber for this node, only ensure it is healthy
// when it now owns slots with active channel subscriptions.
if (existingSubscriber && existingSubscriber.isHealthy()) {
debug("Skipping creating new subscriber for %s", nodeKey);
if (!existingSubscriber.isStarted() &&
this.shouldStartSubscriber(existingSubscriber)) {
startPromises.push(existingSubscriber
.start()
.then(() => {
this.handleSubscriberConnectSucceeded(nodeKey);
})
.catch((error) => {
this.handleSubscriberConnectFailed(error, nodeKey);
}));
}
continue;
}
// If we have an existing subscriber but it is not healthy, stop it
if (existingSubscriber && !existingSubscriber.isHealthy()) {
debug("Replacing subscriber for %s", nodeKey);
existingSubscriber.stop();
this.shardedSubscribers.delete(nodeKey);
this.subscriberGroupEmitter.emit("-subscriber");
}
debug("Creating new subscriber for %s", nodeKey);
// Otherwise create a new subscriber
const redis = clusterNodes.find((node) => {
return (0, util_1.getNodeKey)(node.options) === nodeKey;
});
if (!redis) {
debug("Failed to find node for key %s", nodeKey);
continue;
}
const sub = new ShardedSubscriber_1.default(this.subscriberGroupEmitter, redis.options, this.options.redisOptions);
this.shardedSubscribers.set(nodeKey, sub);
if (this.shouldStartSubscriber(sub)) {
startPromises.push(sub
.start()
.then(() => {
this.handleSubscriberConnectSucceeded(nodeKey);
})
.catch((error) => {
this.handleSubscriberConnectFailed(error, nodeKey);
}));
}
this.subscriberGroupEmitter.emit("+subscriber");
}
// It's vital to await the start promises before resubscribing
// Otherwise we might try to resubscribe to a subscriber that is not yet connected
// This can cause a race condition
await Promise.all(startPromises);
this._resubscribe();
this.subscriberGroupEmitter.emit("subscribersReady");
}
finally {
this.isResetting = false;
if (this.pendingReset) {
const { slots, nodes } = this.pendingReset;
this.pendingReset = null;
await this.reset(slots, nodes);
}
}
}
/**
* Refreshes the subscriber-related slot ranges
*
* Returns false if no refresh was needed
*
* @param targetSlots
*/
_refreshSlots(targetSlots) {
//If there was an actual change, then reassign the slot ranges
// Also rebuild if subscriberToSlotsIndex is empty (e.g., after stop() was called)
if (this._slotsAreEqual(targetSlots) && this.subscriberToSlotsIndex.size > 0) {
debug("Nothing to refresh because the new cluster map is equal to the previous one.");
return false;
}
debug("Refreshing the slots of the subscriber group.");
//Rebuild the slots index
this.subscriberToSlotsIndex = new Map();
for (let slot = 0; slot < targetSlots.length; slot++) {
const node = targetSlots[slot][0];
if (!this.subscriberToSlotsIndex.has(node)) {
this.subscriberToSlotsIndex.set(node, []);
}
this.subscriberToSlotsIndex.get(node).push(Number(slot));
}
//Update the cached slots map
this.clusterSlots = JSON.parse(JSON.stringify(targetSlots));
return true;
}
/**
* Resubscribes to the previous channels
*
* @private
*/
_resubscribe() {
if (this.shardedSubscribers) {
this.shardedSubscribers.forEach((s, nodeKey) => {
const subscriberSlots = this.subscriberToSlotsIndex.get(nodeKey);
if (subscriberSlots) {
//Resubscribe on the underlying connection
subscriberSlots.forEach((ss) => {
//Might return null if being disconnected
const redis = s.getInstance();
const channels = this.channels.get(ss);
if (channels && channels.length > 0) {
if (!redis || redis.status === "end") {
return;
}
if (redis.status === "ready") {
redis.ssubscribe(...channels).catch((err) => {
// TODO: Should we emit an error event here?
debug("Failed to ssubscribe on node %s: %s", nodeKey, err);
});
}
else {
redis.once("ready", () => {
redis.ssubscribe(...channels).catch((err) => {
// TODO: Should we emit an error event here?
debug("Failed to ssubscribe on node %s: %s", nodeKey, err);
});
});
}
}
});
}
});
}
}
/**
* Deep equality of the cluster slots objects
*
* @param other
* @private
*/
_slotsAreEqual(other) {
if (this.clusterSlots === undefined) {
return false;
}
else {
return JSON.stringify(this.clusterSlots) === JSON.stringify(other);
}
}
/**
* Checks if any subscribers are in an unhealthy state.
*
* A subscriber is considered unhealthy if:
* - It exists but is not started (failed/disconnected)
* - It's missing entirely for a node that should have one
*
* @returns true if any subscribers need to be recreated
*/
hasUnhealthySubscribers() {
const hasFailedSubscribers = Array.from(this.shardedSubscribers.values()).some((sub) => !sub.isHealthy());
const hasMissingSubscribers = Array.from(this.subscriberToSlotsIndex.keys()).some((nodeKey) => !this.shardedSubscribers.has(nodeKey));
return hasFailedSubscribers || hasMissingSubscribers;
}
shouldStartSubscriber(sub) {
if (sub.isStarted()) {
return false;
}
if (!sub.isLazyConnect()) {
return true;
}
const subscriberSlots = this.subscriberToSlotsIndex.get(sub.getNodeKey());
if (!subscriberSlots) {
return false;
}
return subscriberSlots.some((slot) => {
const channels = this.channels.get(slot);
return Boolean(channels && channels.length > 0);
});
}
}
// Retry strategy
ClusterSubscriberGroup.MAX_RETRY_ATTEMPTS = 10;
ClusterSubscriberGroup.MAX_BACKOFF_MS = 2000;
ClusterSubscriberGroup.BASE_BACKOFF_MS = 100;
exports.default = ClusterSubscriberGroup;

51
node_modules/ioredis/built/cluster/ConnectionPool.d.ts generated vendored Normal file
View File

@@ -0,0 +1,51 @@
/// <reference types="node" />
import { EventEmitter } from "events";
import { RedisOptions, NodeKey, NodeRole } from "./util";
import { ClusterOptions, ClusterNodeRetryStrategy } from "./ClusterOptions";
import Redis from "../Redis";
export interface ConnectionPoolHooks {
onCreate?: (redis: Redis, readOnly: boolean) => void;
onRoleChange?: (redis: Redis, key: NodeKey, readOnly: boolean) => void;
onRemove?: (redis: Redis) => void;
}
export default class ConnectionPool extends EventEmitter {
private redisOptions;
private clusterNodeRetryStrategy;
private hooks;
private nodes;
private specifiedOptions;
constructor(redisOptions: NonNullable<ClusterOptions["redisOptions"]>, clusterNodeRetryStrategy?: ClusterNodeRetryStrategy, hooks?: ConnectionPoolHooks);
getNodes(role?: NodeRole): Redis[];
getInstanceByKey(key: NodeKey): Redis;
getSampleInstance(role: NodeRole): Redis;
/**
* Add a master node to the pool
* @param node
*/
addMasterNode(node: RedisOptions): boolean;
/**
* Creates a Redis connection instance from the node options
* @param node
* @param readOnly
*/
createRedisFromOptions(node: RedisOptions, readOnly: boolean): Redis<import("../types").ReplyMappingMode>;
/**
* Find or create a connection to the node
*/
findOrCreate(node: RedisOptions, readOnly?: boolean): Redis;
/**
* Replace the connection to the node with a fresh one. Used when the
* existing connection is known to be stale, e.g. after a MOVED redirect
* that points back at the node it came from.
*/
recreate(node: RedisOptions, readOnly?: boolean): Redis;
/**
* Reset the pool with a set of nodes.
* The old node will be removed.
*/
reset(nodes: RedisOptions[]): void;
/**
* Remove a node from the pool.
*/
private removeNode;
}

190
node_modules/ioredis/built/cluster/ConnectionPool.js generated vendored Normal file
View File

@@ -0,0 +1,190 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const utils_1 = require("../utils");
const util_1 = require("./util");
const Redis_1 = require("../Redis");
const debug = (0, utils_1.Debug)("cluster:connectionPool");
class ConnectionPool extends events_1.EventEmitter {
constructor(redisOptions, clusterNodeRetryStrategy = null, hooks = {}) {
super();
this.redisOptions = redisOptions;
this.clusterNodeRetryStrategy = clusterNodeRetryStrategy;
this.hooks = hooks;
// master + slave = all
this.nodes = {
all: {},
master: {},
slave: {},
};
this.specifiedOptions = {};
}
getNodes(role = "all") {
const nodes = this.nodes[role];
return Object.keys(nodes).map((key) => nodes[key]);
}
getInstanceByKey(key) {
return this.nodes.all[key];
}
getSampleInstance(role) {
const keys = Object.keys(this.nodes[role]);
const sampleKey = (0, utils_1.sample)(keys);
return this.nodes[role][sampleKey];
}
/**
* Add a master node to the pool
* @param node
*/
addMasterNode(node) {
const key = (0, util_1.getNodeKey)(node.options);
const redis = this.createRedisFromOptions(node, node.options.readOnly);
//Master nodes aren't read-only
if (!node.options.readOnly) {
this.nodes.all[key] = redis;
this.nodes.master[key] = redis;
return true;
}
return false;
}
/**
* Creates a Redis connection instance from the node options
* @param node
* @param readOnly
*/
createRedisFromOptions(node, readOnly) {
const redis = new Redis_1.default((0, utils_1.defaults)({
// By default, never try to reconnect when a node is lost,
// instead, waiting for a `MOVED` error and fetching slots again.
// When `clusterNodeRetryStrategy` is set, use it to allow
// reconnection (e.g. for replica nodes that restart without
// any slot changes).
retryStrategy: typeof this.clusterNodeRetryStrategy === "function"
? this.clusterNodeRetryStrategy
: null,
// Offline queue should be enabled so that
// we don't need to wait for the `ready` event
// before sending commands to the node.
enableOfflineQueue: true,
readOnly: readOnly,
}, node, this.redisOptions, { lazyConnect: true }));
this.hooks.onCreate?.(redis, readOnly);
return redis;
}
/**
* Find or create a connection to the node
*/
findOrCreate(node, readOnly = false) {
const key = (0, util_1.getNodeKey)(node);
readOnly = Boolean(readOnly);
if (this.specifiedOptions[key]) {
Object.assign(node, this.specifiedOptions[key]);
}
else {
this.specifiedOptions[key] = node;
}
let redis;
if (this.nodes.all[key]) {
redis = this.nodes.all[key];
if (redis.options.readOnly !== readOnly) {
redis.options.readOnly = readOnly;
debug("Change role of %s to %s", key, readOnly ? "slave" : "master");
redis[readOnly ? "readonly" : "readwrite"]().catch(utils_1.noop);
if (readOnly) {
delete this.nodes.master[key];
this.nodes.slave[key] = redis;
}
else {
delete this.nodes.slave[key];
this.nodes.master[key] = redis;
}
this.hooks.onRoleChange?.(redis, key, readOnly);
}
}
else {
debug("Connecting to %s as %s", key, readOnly ? "slave" : "master");
redis = this.createRedisFromOptions(node, readOnly);
this.nodes.all[key] = redis;
this.nodes[readOnly ? "slave" : "master"][key] = redis;
redis.once("end", () => {
if (this.nodes.all[key] && this.nodes.all[key] !== redis) {
// The node was recreated (see `recreate`); leave the new one alone.
return;
}
this.removeNode(key);
this.emit("-node", redis, key);
if (!Object.keys(this.nodes.all).length) {
this.emit("drain");
}
});
this.emit("+node", redis, key);
redis.on("error", (error) => {
this.emit("nodeError", error, key);
});
}
return redis;
}
/**
* Replace the connection to the node with a fresh one. Used when the
* existing connection is known to be stale, e.g. after a MOVED redirect
* that points back at the node it came from.
*/
recreate(node, readOnly = false) {
const key = (0, util_1.getNodeKey)(node);
const existing = this.nodes.all[key];
if (existing) {
debug("Recreating connection to %s", key);
// Remove before disconnecting so the "end" event of the stale
// instance doesn't tear down the replacement.
this.removeNode(key);
existing.disconnect();
}
const redis = this.findOrCreate(node, readOnly);
if (existing) {
// Emit after the replacement is in place so listeners (e.g.
// ClusterSubscriber) never observe the pool without the node.
this.emit("-node", existing, key);
}
return redis;
}
/**
* Reset the pool with a set of nodes.
* The old node will be removed.
*/
reset(nodes) {
debug("Reset with %O", nodes);
const newNodes = {};
nodes.forEach((node) => {
const key = (0, util_1.getNodeKey)(node);
// Don't override the existing (master) node
// when the current one is slave.
if (!(node.readOnly && newNodes[key])) {
newNodes[key] = node;
}
});
Object.keys(this.nodes.all).forEach((key) => {
if (!newNodes[key]) {
debug("Disconnect %s because the node does not hold any slot", key);
this.nodes.all[key].disconnect();
this.removeNode(key);
}
});
Object.keys(newNodes).forEach((key) => {
const node = newNodes[key];
this.findOrCreate(node, node.readOnly);
});
}
/**
* Remove a node from the pool.
*/
removeNode(key) {
const { nodes } = this;
if (nodes.all[key]) {
debug("Remove %s from the pool", key);
this.hooks.onRemove?.(nodes.all[key]);
delete nodes.all[key];
}
delete nodes.master[key];
delete nodes.slave[key];
}
}
exports.default = ConnectionPool;

20
node_modules/ioredis/built/cluster/DelayQueue.d.ts generated vendored Normal file
View File

@@ -0,0 +1,20 @@
export interface DelayQueueOptions {
callback?: Function;
timeout: number;
}
/**
* Queue that runs items after specified duration
*/
export default class DelayQueue {
private queues;
private timeouts;
/**
* Add a new item to the queue
*
* @param bucket bucket name
* @param item function that will run later
* @param options
*/
push(bucket: string, item: Function, options: DelayQueueOptions): void;
private execute;
}

53
node_modules/ioredis/built/cluster/DelayQueue.js generated vendored Normal file
View File

@@ -0,0 +1,53 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const Deque = require("denque");
const debug = (0, utils_1.Debug)("delayqueue");
/**
* Queue that runs items after specified duration
*/
class DelayQueue {
constructor() {
this.queues = {};
this.timeouts = {};
}
/**
* Add a new item to the queue
*
* @param bucket bucket name
* @param item function that will run later
* @param options
*/
push(bucket, item, options) {
const callback = options.callback || process.nextTick;
if (!this.queues[bucket]) {
this.queues[bucket] = new Deque();
}
const queue = this.queues[bucket];
queue.push(item);
if (!this.timeouts[bucket]) {
this.timeouts[bucket] = setTimeout(() => {
callback(() => {
this.timeouts[bucket] = null;
this.execute(bucket);
});
}, options.timeout);
}
}
execute(bucket) {
const queue = this.queues[bucket];
if (!queue) {
return;
}
const { length } = queue;
if (!length) {
return;
}
debug("send %d commands in %s queue", length, bucket);
this.queues[bucket] = null;
while (queue.length > 0) {
queue.shift()();
}
}
}
exports.default = DelayQueue;

View File

@@ -0,0 +1,36 @@
/// <reference types="node" />
import EventEmitter = require("events");
import { RedisOptions } from "./util";
import Redis from "../Redis";
import { ClusterOptions } from "./ClusterOptions";
declare const SubscriberStatus: {
readonly IDLE: "idle";
readonly STARTING: "starting";
readonly CONNECTED: "connected";
readonly STOPPING: "stopping";
readonly ENDED: "ended";
};
type SubscriberStatus = typeof SubscriberStatus[keyof typeof SubscriberStatus];
export default class ShardedSubscriber {
private readonly emitter;
private readonly nodeKey;
private status;
private instance;
private connectPromise;
private lazyConnect;
private readonly messageListeners;
constructor(emitter: EventEmitter, options: RedisOptions, redisOptions?: ClusterOptions["redisOptions"]);
start(): Promise<void>;
stop(): void;
isStarted(): boolean;
get subscriberStatus(): SubscriberStatus;
isHealthy(): boolean;
getInstance(): Redis | null;
getNodeKey(): string;
isLazyConnect(): boolean;
private onEnd;
private onError;
private onMoved;
private updateStatus;
}
export {};

146
node_modules/ioredis/built/cluster/ShardedSubscriber.js generated vendored Normal file
View File

@@ -0,0 +1,146 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("./util");
const utils_1 = require("../utils");
const Redis_1 = require("../Redis");
const debug = (0, utils_1.Debug)("cluster:subscriberGroup:shardedSubscriber");
const SubscriberStatus = {
IDLE: "idle",
STARTING: "starting",
CONNECTED: "connected",
STOPPING: "stopping",
ENDED: "ended",
};
const ALLOWED_STATUS_UPDATES = {
[SubscriberStatus.IDLE]: [
SubscriberStatus.STARTING,
SubscriberStatus.STOPPING,
SubscriberStatus.ENDED,
],
[SubscriberStatus.STARTING]: [
SubscriberStatus.CONNECTED,
SubscriberStatus.STOPPING,
SubscriberStatus.ENDED,
],
[SubscriberStatus.CONNECTED]: [
SubscriberStatus.STOPPING,
SubscriberStatus.ENDED,
],
[SubscriberStatus.STOPPING]: [SubscriberStatus.ENDED],
[SubscriberStatus.ENDED]: [],
};
class ShardedSubscriber {
constructor(emitter, options, redisOptions) {
this.emitter = emitter;
this.status = SubscriberStatus.IDLE;
this.instance = null;
this.connectPromise = null;
// Store listener references for cleanup
this.messageListeners = new Map();
this.onEnd = () => {
this.updateStatus(SubscriberStatus.ENDED);
this.emitter.emit("-node", this.instance, this.nodeKey);
};
this.onError = (error) => {
this.emitter.emit("nodeError", error, this.nodeKey);
};
this.onMoved = () => {
this.emitter.emit("moved");
};
this.instance = new Redis_1.default((0, utils_1.defaults)({
enableReadyCheck: false,
enableOfflineQueue: true,
connectionName: (0, util_1.getConnectionName)("ssubscriber", options.connectionName),
/**
* Disable auto reconnection for subscribers.
* The ClusterSubscriberGroup will handle the reconnection.
*/
retryStrategy: null,
lazyConnect: true,
}, options, redisOptions));
this.lazyConnect = redisOptions?.lazyConnect ?? true;
this.nodeKey = (0, util_1.getNodeKey)(options);
// Register listeners
this.instance.on("end", this.onEnd);
this.instance.on("error", this.onError);
this.instance.on("moved", this.onMoved);
for (const event of ["smessage", "smessageBuffer"]) {
const listener = (...args) => {
this.emitter.emit(event, ...args);
};
this.messageListeners.set(event, listener);
this.instance.on(event, listener);
}
}
async start() {
if (this.connectPromise) {
return this.connectPromise;
}
if (this.status === SubscriberStatus.STARTING ||
this.status === SubscriberStatus.CONNECTED) {
return;
}
if (this.status === SubscriberStatus.ENDED || !this.instance) {
throw new Error(`Sharded subscriber ${this.nodeKey} cannot be restarted once ended.`);
}
this.updateStatus(SubscriberStatus.STARTING);
this.connectPromise = this.instance.connect();
try {
await this.connectPromise;
this.updateStatus(SubscriberStatus.CONNECTED);
}
catch (err) {
this.updateStatus(SubscriberStatus.ENDED);
throw err;
}
finally {
this.connectPromise = null;
}
}
stop() {
this.updateStatus(SubscriberStatus.STOPPING);
if (this.instance) {
this.instance.disconnect();
this.instance.removeAllListeners();
this.messageListeners.clear();
this.instance = null;
}
this.updateStatus(SubscriberStatus.ENDED);
debug("stopped %s", this.nodeKey);
}
isStarted() {
return [
SubscriberStatus.CONNECTED,
SubscriberStatus.STARTING,
].includes(this.status);
}
get subscriberStatus() {
return this.status;
}
isHealthy() {
return ((this.status === SubscriberStatus.IDLE ||
this.status === SubscriberStatus.CONNECTED ||
this.status === SubscriberStatus.STARTING) &&
this.instance !== null);
}
getInstance() {
return this.instance;
}
getNodeKey() {
return this.nodeKey;
}
isLazyConnect() {
return this.lazyConnect;
}
updateStatus(nextStatus) {
if (this.status === nextStatus) {
return;
}
if (!ALLOWED_STATUS_UPDATES[this.status].includes(nextStatus)) {
debug("Invalid status transition for %s: %s -> %s", this.nodeKey, this.status, nextStatus);
return;
}
this.status = nextStatus;
}
}
exports.default = ShardedSubscriber;

166
node_modules/ioredis/built/cluster/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,166 @@
/// <reference types="node" />
import { EventEmitter } from "events";
import Command from "../Command";
import Redis from "../Redis";
import ScanStream from "../ScanStream";
import { Transaction } from "../transaction";
import { Callback, ReplyMappingFromClusterOptions, ReplyMappingMode, ScanStreamOptions, WriteableStream } from "../types";
import Commander from "../utils/Commander";
import { ClusterOptions, ClusterOptionsWithReplyMapping } from "./ClusterOptions";
import { NodeKey, NodeRole } from "./util";
export type ClusterNode = string | number | {
host?: string | undefined;
port?: number | undefined;
};
export type ClusterStatus = "end" | "close" | "wait" | "connecting" | "connect" | "ready" | "reconnecting" | "disconnecting";
/**
* Client for the official Redis Cluster
*/
declare class Cluster<ReplyMapping extends ReplyMappingMode = "legacy"> extends Commander<{
type: "default";
mapping: ReplyMapping extends "resp3" ? "resp3" : "resp2";
}> {
options: ClusterOptions;
slots: NodeKey[][];
status: ClusterStatus;
/**
* @ignore
*/
_groupsIds: {
[key: string]: number;
};
/**
* @ignore
*/
_groupsBySlot: number[];
/**
* @ignore
*/
isCluster: boolean;
private startupNodes;
private connectionPool;
private manuallyClosing;
private retryAttempts;
private delayQueue;
private offlineQueue;
private subscriber;
private shardedSubscribers;
private slotsTimer;
private reconnectTimeout;
private isRefreshing;
private _refreshSlotsCacheCallbacks;
private _autoPipelines;
private _runningAutoPipelines;
private _readyDelayedCallbacks;
private subscriberGroupEmitter;
/**
* Every time Cluster#connect() is called, this value will be
* auto-incrementing. The purpose of this value is used for
* discarding previous connect attampts when creating a new
* connection.
*/
private connectionEpoch;
/**
* Creates an instance of Cluster.
*/
constructor(startupNodes: ClusterNode[], options?: ClusterOptionsWithReplyMapping<ReplyMapping>);
/**
* Connect to a cluster
*/
connect(): Promise<void>;
/**
* Disconnect from every node in the cluster.
*/
disconnect(reconnect?: boolean): void;
/**
* Quit the cluster gracefully.
*/
quit(callback?: Callback<"OK">): Promise<"OK">;
/**
* Create a new instance with the same startup nodes and options as the current one.
*
* @example
* ```js
* var cluster = new Redis.Cluster([{ host: "127.0.0.1", port: "30001" }]);
* var anotherCluster = cluster.duplicate();
* ```
*/
duplicate<OverrideOptions extends ClusterOptionsWithReplyMapping<ReplyMappingMode> | undefined = undefined>(overrideStartupNodes?: ClusterNode[], overrideOptions?: OverrideOptions): Cluster<ReplyMappingFromClusterOptions<ReplyMapping, OverrideOptions>>;
/**
* Get nodes with the specified role
*/
nodes(role?: NodeRole): Redis[];
/**
* This is needed in order not to install a listener for each auto pipeline
*
* @ignore
*/
delayUntilReady(callback: Callback): void;
/**
* Get the number of commands queued in automatic pipelines.
*
* This is not available (and returns 0) until the cluster is connected and slots information have been received.
*/
get autoPipelineQueueSize(): number;
/**
* Refresh the slot cache
*
* @ignore
*/
refreshSlotsCache(callback?: Callback<void>): void;
/**
* @ignore
*/
sendCommand(command: Command, stream?: WriteableStream, node?: any): unknown;
sscanStream(key: string, options?: ScanStreamOptions): ScanStream;
sscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
hscanStream(key: string, options?: ScanStreamOptions): ScanStream;
hscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
zscanStream(key: string, options?: ScanStreamOptions): ScanStream;
zscanBufferStream(key: string, options?: ScanStreamOptions): ScanStream;
/**
* @ignore
*/
handleError(error: Error, ttl: {
value?: any;
}, handlers: any): void;
private resetOfflineQueue;
private clearNodesRefreshInterval;
private resetNodesRefreshInterval;
/**
* Change cluster instance's status
*/
private setStatus;
/**
* Called when closed to check whether a reconnection should be made
*/
private handleCloseEvent;
/**
* Flush offline queue with error.
*/
private flushQueue;
private executeOfflineCommands;
private natMapper;
private getInfoFromNode;
private invokeReadyDelayedCallbacks;
/**
* Check whether Cluster is able to process commands
*/
private readyCheck;
private resolveSrv;
private dnsLookup;
/**
* Normalize startup nodes, and resolving hostnames to IPs.
*
* This process happens every time when #connect() is called since
* #startupNodes and DNS records may chanage.
*/
private resolveStartupNodeHostnames;
private createScanStream;
private createShardedSubscriberGroup;
}
interface Cluster<ReplyMapping extends "legacy" | "resp3" = "legacy"> extends EventEmitter {
}
interface Cluster<ReplyMapping extends "legacy" | "resp3" = "legacy"> extends Transaction<ReplyMapping extends "resp3" ? "resp3" : "resp2"> {
}
export default Cluster;

1049
node_modules/ioredis/built/cluster/index.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

27
node_modules/ioredis/built/cluster/util.d.ts generated vendored Normal file
View File

@@ -0,0 +1,27 @@
/// <reference types="node" />
import type Redis from "../Redis";
import { SrvRecord } from "dns";
export type NodeKey = string;
export type NodeRole = "master" | "slave" | "all";
export interface RedisOptions {
port: number;
host: string;
username?: string | undefined;
password?: string | undefined;
[key: string]: any;
}
export interface SrvRecordsGroup {
totalWeight: number;
records: SrvRecord[];
}
export interface GroupedSrvRecords {
[key: number]: SrvRecordsGroup;
}
export declare function waitForRedisReady(redis: Redis): Promise<void>;
export declare function getNodeKey(node: RedisOptions): NodeKey;
export declare function nodeKeyToRedisOptions(nodeKey: NodeKey): RedisOptions;
export declare function normalizeNodeOptions(nodes: Array<string | number | object>): RedisOptions[];
export declare function getUniqueHostnamesFromOptions(nodes: RedisOptions[]): string[];
export declare function groupSrvRecords(records: SrvRecord[]): GroupedSrvRecords;
export declare function weightSrvRecords(recordsGroup: SrvRecordsGroup): SrvRecord;
export declare function getConnectionName(component: any, nodeConnectionName: any): string;

124
node_modules/ioredis/built/cluster/util.js generated vendored Normal file
View File

@@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getConnectionName = exports.weightSrvRecords = exports.groupSrvRecords = exports.getUniqueHostnamesFromOptions = exports.normalizeNodeOptions = exports.nodeKeyToRedisOptions = exports.getNodeKey = exports.waitForRedisReady = void 0;
const utils_1 = require("../utils");
const net_1 = require("net");
function waitForRedisReady(redis) {
if (redis.status === "ready") {
return Promise.resolve();
}
if (redis.status === "wait") {
return redis.connect();
}
if (redis.status === "end") {
return Promise.reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
}
return new Promise((resolve, reject) => {
const onReady = () => {
redis.removeListener("end", onEnd);
resolve();
};
const onEnd = () => {
redis.removeListener("ready", onReady);
reject(new Error(utils_1.CONNECTION_CLOSED_ERROR_MSG));
};
redis.once("ready", onReady);
redis.once("end", onEnd);
});
}
exports.waitForRedisReady = waitForRedisReady;
function getNodeKey(node) {
node.port = node.port || 6379;
node.host = node.host || "127.0.0.1";
return node.host + ":" + node.port;
}
exports.getNodeKey = getNodeKey;
function nodeKeyToRedisOptions(nodeKey) {
const portIndex = nodeKey.lastIndexOf(":");
if (portIndex === -1) {
throw new Error(`Invalid node key ${nodeKey}`);
}
return {
host: nodeKey.slice(0, portIndex),
port: Number(nodeKey.slice(portIndex + 1)),
};
}
exports.nodeKeyToRedisOptions = nodeKeyToRedisOptions;
function normalizeNodeOptions(nodes) {
return nodes.map((node) => {
const options = {};
if (typeof node === "object") {
Object.assign(options, node);
}
else if (typeof node === "string") {
Object.assign(options, (0, utils_1.parseURL)(node));
}
else if (typeof node === "number") {
options.port = node;
}
else {
throw new Error("Invalid argument " + node);
}
if (typeof options.port === "string") {
options.port = parseInt(options.port, 10);
}
// Cluster mode only support db 0
delete options.db;
if (!options.port) {
options.port = 6379;
}
if (!options.host) {
options.host = "127.0.0.1";
}
return (0, utils_1.resolveTLSProfile)(options);
});
}
exports.normalizeNodeOptions = normalizeNodeOptions;
function getUniqueHostnamesFromOptions(nodes) {
const uniqueHostsMap = {};
nodes.forEach((node) => {
uniqueHostsMap[node.host] = true;
});
return Object.keys(uniqueHostsMap).filter((host) => !(0, net_1.isIP)(host));
}
exports.getUniqueHostnamesFromOptions = getUniqueHostnamesFromOptions;
function groupSrvRecords(records) {
const recordsByPriority = {};
for (const record of records) {
if (!recordsByPriority.hasOwnProperty(record.priority)) {
recordsByPriority[record.priority] = {
totalWeight: record.weight,
records: [record],
};
}
else {
recordsByPriority[record.priority].totalWeight += record.weight;
recordsByPriority[record.priority].records.push(record);
}
}
return recordsByPriority;
}
exports.groupSrvRecords = groupSrvRecords;
function weightSrvRecords(recordsGroup) {
if (recordsGroup.records.length === 1) {
recordsGroup.totalWeight = 0;
return recordsGroup.records.shift();
}
// + `recordsGroup.records.length` to support `weight` 0
const random = Math.floor(Math.random() * (recordsGroup.totalWeight + recordsGroup.records.length));
let total = 0;
for (const [i, record] of recordsGroup.records.entries()) {
total += 1 + record.weight;
if (total > random) {
recordsGroup.totalWeight -= record.weight;
recordsGroup.records.splice(i, 1);
return record;
}
}
}
exports.weightSrvRecords = weightSrvRecords;
function getConnectionName(component, nodeConnectionName) {
const prefix = `ioredis-cluster(${component})`;
return nodeConnectionName ? `${prefix}:${nodeConnectionName}` : prefix;
}
exports.getConnectionName = getConnectionName;