import { EventEmitter } from 'node:events'; import type { ClientConfigOrString, ClientCredentials, CommandResponse, PasswordCredentials, RawClient, SendCommandOptions, TokenCredentials } from '../client/client.type.js'; export declare class VsrResponseTimeoutError extends Error { constructor(timeout: number); } /** * Manages command execution and response handling for the Iggy server. * Implements command queuing, authentication, and heartbeat functionality. */ export declare class CommandResponseStream extends EventEmitter { /** Client configuration */ private options; /** Underlying connection to the server */ private connection; /** Queue of pending command jobs */ private _execQueue; /** Consensus session used by VSR framing */ private vsrSession; /** Shared authentication attempt for concurrent callers */ private authenticationPromise?; /** Whether a login is already being moved to the leader */ private settlingLeader; /** * The leader re-check a refused request started, shared with every other * request refused by the same node so one demotion moves the client once. */ private leaderMoveInFlight?; /** * Set when a roster walk just redirected this client, so the login that * re-authenticates it stays on the dialed node instead of settling back on * the metadata leader whose partition replica refused the request. */ private walkSettleSuppressed; /** * Refusals handed out to callers that have not decided what to do with them * yet. The queue holds while any are outstanding: the caller of a refused * command re-checks the leader, and a command written in the meantime goes * out on the socket that check is about to replace. */ private leaderMovesUndecided; /** How long a leaderless roster is polled before settling in place */ private leaderlessWaitBudget; /** Delay between roster reads while the cluster elects */ private leaderlessPollInterval; /** Calls that have acquired this stream but have not fully settled */ private pendingSubmissions; /** Whether the stream is currently processing a command */ busy: boolean; /** Whether the client has been authenticated */ isAuthenticated: boolean; /** Authenticated user ID */ userId?: number; /** Heartbeat interval timer handle */ heartbeatIntervalHandler?: NodeJS.Timeout; /** Whether a heartbeat ping is still awaiting its response */ private heartbeatInFlight; private rememberedCredentials?; private clustered?; private metadataWatermark; private routingGeneration; private pollRoutes; private pollConnections; /** * Creates a new CommandResponseStream. * * @param options - Client configuration */ constructor(options: ClientConfigOrString); /** * Initializes the stream by setting up heartbeat and connection event handlers. */ _init(): void; /** * Re-submits queued commands through the full send path, so each one * reconnects, re-authenticates and re-checks the leader as if it had just * been called. * * Only for a drop the client caused. Nothing here was written, so there is no * outcome in doubt: a command still in the queue when the socket is replaced * would otherwise fail with a lost-connection error the caller can do nothing * about. */ private _reissueQueue; /** * Sends a command to the server. * Automatically handles connection, authentication and leader settlement. * * @param command - Command code to send * @param payload - Command payload buffer * @param options - Response and queue options * @returns Promise resolving to the command response */ sendCommand(command: number, payload: Buffer, options?: SendCommandOptions): Promise; private _queueCommand; /** * Re-reads the roster and moves to the leader it names. * * Best effort: an unreadable roster, or one that still names this node, * leaves the client where it is and the refused request is re-issued anyway. * * Single-flighted, and concurrent callers share the outcome instead of * failing: several commands are refused by the same demoted node, and each * starting its own redirect would move the client once per command. The * first redirect's `'disconnected'` also fails the others' roster reads, so * a caller that raced one would report a refusal it never had to. * * @returns Whether the client moved */ private _followLeaderMove; /** Whether a leader move is being decided or carried out. */ private _movePending; /** * Releases the queue hold one refusal took, and drains what was held back * once the last of them is decided. */ private _releaseUndecidedMove; private _rememberRoster; private _pollOnPrimary; private _pollRoutingControl; private _observeMetadataReply; private _rememberCredentials; private _signInCredentials; private _clearPollRouting; /** * Processes queued commands sequentially. * Emits 'finishQueue' when all commands are processed. * */ _processQueue(): Promise; private _emitFinishQueue; /** * Processes a single command by writing it to the connection and waiting for response. * * @param command - Command code * @param payload - Command payload * @param handleResp - Whether to parse the response * @param deadline - When the whole request gives up, shared with the leader * re-checks and the re-issues after a move * @returns Promise resolving to the command response */ _processNext(command: number, payload: Buffer, handleResp?: boolean, deadline?: number): Promise; private _processVsrLogin; private _processVsr; private _exchange; private isUnloggedCommand; /** * Moves a freshly authenticated session to the cluster leader. * * Only the leader accepts replicated commands, and the roster read is * auth-gated, so the topology cannot be inspected before a login binds a * session. The redirect drops that session along with the socket, so the * login is replayed on the leader and its answer supersedes the one from the * node the client dialed. Leadership can move between the roster read and * the replay, so each freshly bound hop rechecks the roster under a bounded * redirect budget. * * @returns The leader's login response, or undefined when the client stays */ private _settleOnLeader; /** * Reads the cluster roster and picks the endpoint to settle on. * * Best effort: an unreadable roster, `Unauthenticated` included (the session * died between the login and this read), keeps the client on its current * node instead of failing a login that already succeeded. */ private _readLeaderEndpoint; /** * Fails all queued commands with the given error. * * @param err - Error to reject all queued commands with */ _failQueue(err: Error): void; private _resetSession; hold(): () => void; /** * Authenticates the client with the server. * * @param creds - Authentication credentials (token or password) * @returns True if authentication succeeded */ authenticate(creds: ClientCredentials): Promise; private _authenticate; /** * Authenticates using username and password. * * @param creds - Password credentials * @returns Login response with user ID */ _authWithPassword(creds: PasswordCredentials): Promise<{ userId: number; }>; /** * Authenticates using a token. * * @param creds - Token credentials * @returns Login response with user ID */ _authWithToken(creds: TokenCredentials): Promise<{ userId: number; }>; /** * Sends a ping command to the server. * * @returns Ping response */ ping(): Promise; /** * Starts sending periodic heartbeat pings to keep the connection alive. * * @param interval - Heartbeat interval in milliseconds */ heartbeat(interval?: number): void; /** * Returns the underlying socket as a readable stream. * * @returns The connection socket */ getReadStream(): import("node:net").Socket; /** * Destroys the stream and cleans up resources. * Stops heartbeat and destroys the connection. */ destroy(): void; } /** * Creates a new RawClient instance. * * @param options - Client configuration * @returns RawClient instance */ export declare function getRawClient(options: ClientConfigOrString): RawClient; //# sourceMappingURL=client.socket.d.ts.map