/// <reference types="node" />
/// <reference types="node" />
import type { RedisClusterOptions } from '.';
import { RedisClientType } from '../client';
import type { CommandToWrite } from '../client/commands-queue';
import { EventEmitter } from 'node:stream';
import { ChannelListeners } from '../client/pub-sub';
import { RedisArgument, RedisFunctions, RedisModules, RedisScripts, RespVersions, TypeMapping } from '../RESP/types';
import { PooledClientSideCacheProvider } from '../client/cache';
import { FieldsetRegistry } from '../himport/registry';
interface NodeAddress {
    host: string;
    port: number;
}
export type NodeAddressMap = {
    [address: string]: NodeAddress;
} | ((address: string) => NodeAddress | undefined);
export declare const RESUBSCRIBE_LISTENERS_EVENT = "__resubscribeListeners";
/**
 * Sticky-cursor binding: which node served a RediSearch cursor. FT.CURSOR
 * READ/DEL carry no key, so hash-slot routing can't reach the coordinator that
 * minted the cursor — we pin by `address` ("host:port"), the durable handle
 * (clients are recreated on reconnect/topology refresh, addresses aren't).
 */
export interface CursorBinding {
    address: string;
    /**
     * The server's real cursor id, kept as a string: FT cursor ids are uint64
     * and `Number` would lose precision above 2^53. The caller only ever sees
     * the client-minted token this binding is keyed by.
     */
    cursorId: string;
    createdAt: number;
    maxIdleMs?: number;
}
/**
 * One in-flight cluster-wide SCAN chain (see
 * `request-response-policies/scan-cursor.ts`). SCAN cursors are per-node
 * state, so a cluster-wide iteration walks the masters one at a time: the
 * entry pins the node currently being scanned, the real server cursor to
 * resume it with, and the masters already exhausted (tracked by address so a
 * topology refresh mid-scan doesn't rescan or skip nodes that survived).
 */
export interface ScanCursorEntry {
    address: string;
    cursor: string;
    visited: Set<string>;
    createdAt: number;
}
export interface Node<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> {
    address: string;
    client?: RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
    connectPromise?: Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
}
export interface ShardNode<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> extends Node<M, F, S, RESP, TYPE_MAPPING>, NodeAddress {
    id: string;
    readonly: boolean;
}
export interface MasterNode<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> extends ShardNode<M, F, S, RESP, TYPE_MAPPING> {
    pubSub?: {
        connectPromise?: Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
        client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
    };
}
export interface Shard<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> {
    master: MasterNode<M, F, S, RESP, TYPE_MAPPING>;
    replicas?: Array<ShardNode<M, F, S, RESP, TYPE_MAPPING>>;
    nodesIterator?: IterableIterator<ShardNode<M, F, S, RESP, TYPE_MAPPING>>;
}
type PubSubNode<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> = (Omit<Node<M, F, S, RESP, TYPE_MAPPING>, 'client'> & Required<Pick<Node<M, F, S, RESP, TYPE_MAPPING>, 'client'>>);
export declare function groupCommandsByDestination<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping>(commands: CommandToWrite[], slots: Array<Shard<M, F, S, RESP, TYPE_MAPPING>>, fallback?: MasterNode<M, F, S, RESP, TYPE_MAPPING>): {
    byDestination: Map<MasterNode<M, F, S, RESP, TYPE_MAPPING>, CommandToWrite[]>;
    unrouted: CommandToWrite[];
};
/**
 * Splits commands extracted from a dying node's queue into the queued tail of
 * a chain (MULTI/pipeline) whose head is already sent (out of view, in
 * `#waitingForReply`) and everything else, which is safe to relocate as-is.
 *
 * Only used on full node loss: the connection is about to be destroyed, so
 * the in-flight chain's tail is rejected rather than relocated - relocating a
 * fragment of an already-partially-sent chain would run it out of order or
 * split it across two connections. This doesn't apply to partial slot
 * migration, where the source connection survives and keeps its in-flight
 * chain intact.
 */
export declare function splitInFlightChainTail(commands: CommandToWrite[], chainInExecution: symbol | undefined): {
    inFlightChainTail: CommandToWrite[];
    relocatable: CommandToWrite[];
};
export type OnShardedChannelMovedError = (err: unknown, channel: string, listeners?: ChannelListeners) => void;
export default class RedisClusterSlots<M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping> {
    #private;
    slots: Shard<M, F, S, RESP, TYPE_MAPPING>[];
    masters: MasterNode<M, F, S, RESP, TYPE_MAPPING>[];
    replicas: ShardNode<M, F, S, RESP, TYPE_MAPPING>[];
    readonly nodeByAddress: Map<string, ShardNode<M, F, S, RESP, TYPE_MAPPING> | MasterNode<M, F, S, RESP, TYPE_MAPPING>>;
    pubSubNode?: PubSubNode<M, F, S, RESP, TYPE_MAPPING>;
    clientSideCache?: PooledClientSideCacheProvider;
    /** The cluster-wide registry, exposed so `RedisCluster.duplicate()` can share it. */
    get himportRegistry(): FieldsetRegistry;
    smigratedSeqIdsSeen: Set<number>;
    /**
     * Per-instance sticky FT cursor bindings, keyed by the client-minted virtual
     * token (the value the caller holds in place of the server's cursor id).
     * Server cursor ids are minted per node and can collide across shards;
     * client tokens come from one sequence and cannot.
     */
    readonly cursorBindings: Map<string, CursorBinding>;
    /** Per-instance cluster-wide SCAN chains, keyed by the virtual cursor token. */
    readonly scanCursors: Map<string, ScanCursorEntry>;
    get isOpen(): boolean;
    get isReady(): boolean;
    constructor(options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING>, emit: EventEmitter['emit'], clusterClientId: string);
    connect(): Promise<void>;
    nodeClient(node: ShardNode<M, F, S, RESP, TYPE_MAPPING>): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
    rediscover(startWith?: RedisClientType<M, F, S, RESP>, excludedAddresses?: ReadonlySet<string>): Promise<void>;
    /**
     * @deprecated Use `close` instead.
     */
    quit(): Promise<void>;
    /**
     * @deprecated Use `destroy` instead.
     */
    disconnect(): Promise<void>;
    close(): Promise<void>;
    destroy(): void;
    /**
     * All fan-out target nodes (masters + replicas), WITHOUT connecting. The
     * caller connects each node lazily in its own per-node promise so a single
     * failed connect rejects only that node's execution — letting reducers such
     * as `one_succeeded` still see the reachable shards — instead of a `Promise.all`
     * over the connects failing the whole route up front. Excludes the dedicated
     * PubSub connection (not in `masters`/`replicas`).
     */
    getAllNodes(): ShardNode<M, F, S, RESP, TYPE_MAPPING>[];
    /** Master fan-out target nodes, WITHOUT connecting (see {@link getAllNodes}). */
    getAllMasterNodes(): MasterNode<M, F, S, RESP, TYPE_MAPPING>[];
    getClientAndSlotNumber(firstKey: RedisArgument | undefined, isReadonly: boolean | undefined): Promise<{
        client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>;
        slotNumber?: number;
    }>;
    getClientForKey(key: RedisArgument, isReadonly: boolean | undefined): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
    _randomNodeIterator?: IterableIterator<ShardNode<M, F, S, RESP, TYPE_MAPPING>>;
    getRandomNode(): ShardNode<M, F, S, RESP, TYPE_MAPPING>;
    getSlotRandomNode(slotNumber: number): ShardNode<M, F, S, RESP, TYPE_MAPPING>;
    getMasterByAddress(address: string): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>> | undefined;
    /**
     * Reverse-resolve a routed client to its node address. FT.AGGREGATE is
     * keyless, so the plan carries only the client; we need its address to bind
     * the cursor. Clients are few per cluster, so the linear scan is negligible.
     */
    nodeAddressByClient(client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>): string | undefined;
    bindCursor(token: string, binding: Omit<CursorBinding, 'createdAt'>): void;
    lookupCursor(token: string): CursorBinding | undefined;
    evictCursor(token: string): void;
    /**
     * Mint a fresh virtual cursor token (cluster-wide SCAN chains, sticky FT
     * cursors). Tokens are what the client hands back to the caller in place of
     * the per-node server cursor: opaque, non-"0", never colliding with each
     * other. A plain counter keeps them valid-looking cursor values for callers
     * that treat the cursor as an opaque number.
     */
    mintCursorToken(): string;
    bindScanCursor(token: string, address: string, cursor: string, visited: Set<string>): void;
    lookupScanCursor(token: string): ScanCursorEntry | undefined;
    evictScanCursor(token: string): void;
    /**
     * First master (in current topology order) whose address is not in
     * `visited` — the next node a cluster-wide SCAN chain should walk.
     */
    nextScanTarget(visited: ReadonlySet<string>): string | undefined;
    getPubSubClient(): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
    executeUnsubscribeCommand(unsubscribe: (client: RedisClientType<M, F, S, RESP>) => Promise<void>): Promise<void>;
    getShardedPubSubClient(channel: string): Promise<RedisClientType<M, F, S, RESP, TYPE_MAPPING>>;
    executeShardedUnsubscribeCommand(channel: string, unsubscribe: (client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>) => Promise<void>): Promise<void>;
}
export {};
//# sourceMappingURL=cluster-slots.d.ts.map