@hpkv/websocket-client
Version:
HPKV WebSocket client for Node.js
697 lines (683 loc) • 24 kB
TypeScript
/**
* @hpkv/websocket-client v1.4.1
* HPKV WebSocket client for Node.js
* @license MIT
*/
/**
* Common interface for both Node.js and Browser WebSockets
*/
interface IWebSocket {
readyState: number;
on(event: string, listener: (...args: any[]) => void): IWebSocket;
removeListener(event: string, listener: (...args: any[]) => void): IWebSocket;
removeAllListeners(): IWebSocket;
send(data: string): void;
close(code?: number, reason?: string): void;
}
/**
* Constants to match WebSocket states across environments
*/
declare const WS_CONSTANTS: {
readonly CONNECTING: 0;
readonly OPEN: 1;
readonly CLOSING: 2;
readonly CLOSED: 3;
};
/**
* Connection state for WebSocket client
*/
declare enum ConnectionState {
DISCONNECTED = "DISCONNECTED",
CONNECTING = "CONNECTING",
CONNECTED = "CONNECTED",
DISCONNECTING = "DISCONNECTING",
RECONNECTING = "RECONNECTING"
}
/**
* Configuration for the exponential backoff retry strategy
*/
interface RetryConfig {
/** Maximum number of reconnection attempts before giving up */
maxReconnectAttempts?: number;
/** Initial delay in milliseconds between reconnection attempts */
initialDelayBetweenReconnects?: number;
/** Maximum delay in milliseconds between reconnection attempts */
maxDelayBetweenReconnects?: number;
/** Random jitter in milliseconds to add to reconnection delay */
jitterMs?: number;
}
/**
* Configuration for the dynamic throttling mechanism
*/
interface ThrottlingConfig {
enabled: boolean;
rateLimit?: number;
}
/**
* Metrics returned by the throttling manager
*/
interface ThrottlingMetrics {
currentRate: number;
queueLength: number;
}
/**
* Configuration options for WebSocket connection behavior
*/
interface ConnectionConfig extends RetryConfig {
/** Configuration for the throttling mechanism */
throttling?: ThrottlingConfig;
/** Client-side timeout in milliseconds for establishing the WebSocket connection */
connectionTimeout?: number;
}
/**
* Statistics about the current WebSocket connection
*/
interface ConnectionStats {
isConnected: boolean;
reconnectAttempts: number;
messagesPending: number;
connectionState?: ConnectionState;
throttling?: {
currentRate: number;
queueLength: number;
} | null;
}
/**
* Represents the available operations for HPKV database interactions
*/
declare enum HPKVOperation {
GET = 1,
SET = 2,
PATCH = 3,
DELETE = 4,
RANGE = 5,
ATOMIC = 6
}
/**
* Configuration for generating authentication tokens
*/
interface HPKVTokenConfig {
/** Array of key patterns the token will be authorized to subscribe to */
subscribePatterns?: string[];
/** Optional access pattern for key-based permissions */
accessPattern?: string;
}
/**
* Structure of a request message sent to the HPKV server
*/
interface HPKVRequestMessage {
/** Operation to perform */
op: HPKVOperation;
/** Key to operate on */
key: string;
/** Value to set (for SET and PATCH operations) */
value?: string | number;
/** Optional message ID for tracking responses */
messageId?: number;
/** End key for range queries */
endKey?: string;
/** Maximum number of records to return */
limit?: number;
}
/**
* Base response interface with common fields for all response types
*/
interface HPKVBaseResponse {
/** Status code */
code?: number;
/** Human-readable message */
message?: string;
/** ID matching the request message */
messageId?: number;
/** Error message if operation failed */
error?: string;
}
/**
* Response for GET operations
*/
interface HPKVGetResponse extends HPKVBaseResponse {
/** Key that was operated on */
key: string;
/** Value retrieved */
value: string | number;
}
/**
* Response for SET operations
*/
interface HPKVSetResponse extends HPKVBaseResponse {
/** Whether the operation was successful */
success: boolean;
}
/**
* Response for PATCH operations
*/
interface HPKVPatchResponse extends HPKVBaseResponse {
/** Whether the operation was successful */
success: boolean;
}
/**
* Response for DELETE operations
*/
interface HPKVDeleteResponse extends HPKVBaseResponse {
/** Whether the operation was successful */
success: boolean;
}
/**
* Response for RANGE operations
*/
interface HPKVRangeResponse extends HPKVBaseResponse {
/** Records returned for RANGE queries */
records: Array<{
key: string;
value: string;
}>;
/** Number of records returned */
count: number;
/** Whether the result was truncated due to size limits */
truncated: boolean;
}
/**
* Response for ATOMIC operations
*/
interface HPKVAtomicResponse extends HPKVBaseResponse {
/** Whether the operation was successful */
success: boolean;
/** Key that was operated on */
key?: string;
/** New value after atomic operation */
newValue: number;
}
/**
* Response for key notifications (pub-sub)
*/
interface HPKVNotificationResponse {
/** Type of response */
type: 'notification';
/** Key that was operated on */
key: string;
/** Value retrieved (null if key was deleted) */
value: string | number | null;
/** Timestamp of the operation */
timestamp: number;
}
/**
* Response for error responses
*/
interface HPKVErrorResponse extends HPKVBaseResponse {
/** Error message if operation failed */
error: string;
}
/**
* Union type for all possible HPKV response types
*/
type HPKVResponse = HPKVGetResponse | HPKVSetResponse | HPKVPatchResponse | HPKVDeleteResponse | HPKVRangeResponse | HPKVAtomicResponse | HPKVNotificationResponse | HPKVErrorResponse;
/**
* Interface for a pending request
*/
interface PendingRequest {
resolve: (value: HPKVResponse) => void;
reject: (reason?: unknown) => void;
timer: NodeJS.Timeout | number;
timestamp: number;
operation: string;
}
/**
* Event handler function type for HPKV responses
*/
type HPKVEventHandler = (data: HPKVNotificationResponse) => void;
/**
* Options for configuring range queries
*/
interface RangeQueryOptions {
/** Maximum number of records to return */
limit?: number;
}
/**
* SimpleEventEmitter
*
* A lightweight implementation of the EventEmitter pattern.
* Provides methods to register event listeners, emit events, and manage subscriptions.
*/
declare class SimpleEventEmitter {
private events;
/**
* Register an event listener for the specified event
*
* @param event - The event name to listen for
* @param listener - The callback function to execute when the event is emitted
* @returns The emitter instance for chaining
*/
on(event: string, listener: (...args: any[]) => void): this;
/**
* Remove a previously registered event listener
*
* @param event - The event name
* @param listener - The callback function to remove
* @returns The emitter instance for chaining
*/
off(event: string, listener: (...args: any[]) => void): this;
/**
* Emit an event with the specified arguments
*
* @param event - The event name to emit
* @param args - Arguments to pass to the event listeners
* @returns `true` if the event had listeners, `false` otherwise
*/
emit(event: string, ...args: any[]): boolean;
}
/**
* Defines default timeout values in milliseconds
*/
declare const DEFAULT_TIMEOUTS: {
readonly OPERATION: 10000;
readonly CLEANUP: 60000;
};
/**
* Manages WebSocket message handling and pending requests
*/
declare class MessageHandler {
private messageId;
private messageMap;
private timeouts;
private cleanupInterval;
/**
* Callback to be invoked when a rate limit error (e.g., 429) is detected.
* The consumer (e.g., BaseWebSocketClient) can set this to react accordingly.
*/
onRateLimitExceeded: ((error: HPKVErrorResponse) => void) | null;
/**
* Creates a new MessageHandler
* @param timeouts - Optional custom timeout values
*/
constructor(timeouts?: Partial<typeof DEFAULT_TIMEOUTS>);
/**
* Initializes the cleanup interval for stale requests
*/
private initCleanupInterval;
/**
* Clears the cleanup interval
*/
private clearCleanupInterval;
/**
* Gets the next message ID, ensuring it doesn't overflow
* @returns A safe message ID number
*/
getNextMessageId(): number;
/**
* Creates a message with an assigned ID
* @param message - Base message without ID
* @returns Message with ID
*/
createMessage(message: Omit<HPKVRequestMessage, 'messageId'>): HPKVRequestMessage;
/**
* Registers a pending request
* @param messageId - The ID of the message
* @param operation - The operation being performed
* @param timeoutMs - Optional custom timeout for this operation
* @returns A promise and cleanup functions
*/
registerRequest(messageId: number, operation: string, timeoutMs?: number): {
promise: Promise<HPKVResponse>;
cancel: (reason: string) => void;
};
/**
* Processes an incoming WebSocket message
* @param message - The message received from the WebSocket server
* @returns True if the message was handled, false if no matching request was found
*/
handleMessage(message: HPKVResponse): void;
/**
* Type guard to check if a response is a notification
*/
private isNotification;
/**
* Type guard to check if a response is an error response
*/
private isErrorResponse;
/**
* Cancels all pending requests with the given error
* @param error - The error to reject pending requests with
*/
cancelAllRequests(error: Error): void;
/**
* Removes stale requests that have been pending for too long
*/
protected cleanupStaleRequests(): void;
/**
* Gets the number of pending requests
*/
get pendingCount(): number;
/**
* Destroys this handler and cleans up resources
*/
destroy(): void;
}
/**
* Manages throttling of requests based on RTT and 429 errors
*/
declare class ThrottlingManager {
private currentRate;
private throttleQueue;
private processingQueue;
private nextAvailableSlotTime;
private backoffUntil;
private backoffExponent;
private throttlingConfig;
constructor(config?: Partial<ThrottlingConfig>);
/**
* Returns current throttling configuration
*/
get config(): ThrottlingConfig;
/**
* Returns current throttling metrics
*/
getMetrics(): ThrottlingMetrics;
/**
* Updates the throttling configuration
*/
updateConfig(config: Partial<ThrottlingConfig>): void;
/**
* Notifies the throttler of a 429 error to apply backpressure
*/
notify429(): void;
/**
* Adds a request to the throttle queue if needed, or executes immediately.
*/
throttleRequest(): Promise<void>;
/**
* Processes the throttle queue based on the current rate
*/
private processThrottleQueue;
/**
* Cleans up resources
*/
destroy(): void;
}
/**
* Base WebSocket client that handles connection management and message passing
* for the HPKV WebSocket API.
*/
declare abstract class BaseWebSocketClient {
protected ws: IWebSocket | null;
protected baseUrl: string;
protected connectionPromise: Promise<void> | null;
protected connectionState: ConnectionState;
protected reconnectAttempts: number;
protected connectionTimeout: NodeJS.Timeout | number | null;
protected emitter: SimpleEventEmitter;
protected messageHandler: MessageHandler;
protected throttlingManager: ThrottlingManager;
protected retry: RetryConfig;
protected isGracefulDisconnect: boolean;
protected connectionTimeoutDuration: number;
/**
* Creates a new BaseWebSocketClient instance
* @param baseUrl - The base URL of the HPKV API
* @param config - The connection configuration including timeouts and retry options
*/
constructor(baseUrl: string, config?: ConnectionConfig);
/**
* Retrieves a value from the key-value store
* @param key - The key to retrieve
* @param timeoutMs - Optional custom timeout for this operation in milliseconds
* @returns A promise that resolves with the API response
* @throws Error if the key is not found or connection fails
*/
get(key: string, timeoutMs?: number): Promise<HPKVGetResponse>;
/**
* Stores a value in the key-value store
* @param key - The key to store the value under
* @param value - The value to store (will be stringified if not a string)
* @param partialUpdate - If true, performs a partial update/patch instead of replacing the entire value
* @param timeoutMs - Optional custom timeout for this operation in milliseconds
* @returns A promise that resolves with the API response
* @throws Error if the operation fails or connection is lost
*/
set(key: string, value: unknown, partialUpdate?: boolean, timeoutMs?: number): Promise<HPKVSetResponse | HPKVPatchResponse>;
/**
* Deletes a value from the key-value store
* @param key - The key to delete
* @param timeoutMs - Optional custom timeout for this operation in milliseconds
* @returns A promise that resolves with the API response
* @throws Error if the key is not found or connection fails
*/
delete(key: string, timeoutMs?: number): Promise<HPKVDeleteResponse>;
/**
* Performs a range query to retrieve multiple keys within a specified range
* @param key - The start key of the range
* @param endKey - The end key of the range
* @param options - Additional options for the range query including result limit
* @param timeoutMs - Optional custom timeout for this operation in milliseconds
* @returns A promise that resolves with the API response containing matching records
* @throws Error if the operation fails or connection is lost
*/
range(key: string, endKey: string, options?: RangeQueryOptions, timeoutMs?: number): Promise<HPKVRangeResponse>;
/**
* Performs an atomic increment operation on a numeric value
* @param key - The key of the value to increment
* @param value - The amount to increment by
* @param timeoutMs - Optional custom timeout for this operation in milliseconds
* @returns A promise that resolves with the API response
* @throws Error if the key does not contain a numeric value or connection fails
*/
atomicIncrement(key: string, value: number, timeoutMs?: number): Promise<HPKVAtomicResponse>;
/**
* Establishes a WebSocket connection to the HPKV API
* @returns A promise that resolves when the connection is established
* @throws ConnectionError if the connection fails or times out
*/
connect(): Promise<void>;
private _initiateConnectionAttempt;
/**
* Gracefully closes the WebSocket connection
* @param cancelPendingRequests - Whether to cancel all pending requests (default: true)
* @returns A promise that resolves when the connection is closed and cleaned up
*/
disconnect(cancelPendingRequests?: boolean): Promise<void>;
/**
* Attempts to reconnect to the WebSocket server with exponential backoff
* @returns A promise that resolves when the connection is reestablished
* @throws ConnectionError if reconnection fails after max attempts
*/
protected reconnect(): Promise<void>;
/**
* Get the current connection state
* @returns The current connection state
*/
getConnectionState(): ConnectionState;
/**
* Get current connection statistics
* @returns Statistics about the current connection
*/
getConnectionStats(): ConnectionStats;
/**
* Checks if the WebSocket connection is currently established and open
* @returns True if the connection is established and ready
*/
private isWebSocketOpen;
/**
* Updates the connection state based on WebSocket readyState
* to ensure consistency between the two state tracking mechanisms
*/
private syncConnectionState;
/**
* Register event listeners
* @param event - The event to listen for
* @param listener - The callback function to execute when the event is emitted
*/
on(event: 'connected' | 'disconnected' | 'reconnecting' | 'reconnectFailed' | 'error', listener: (...args: any[]) => void): void;
/**
* Remove event listeners
* @param event - The event to stop listening for
* @param listener - The callback function to remove
*/
off(event: 'connected' | 'disconnected' | 'reconnecting' | 'reconnectFailed' | 'error', listener: (...args: any[]) => void): void;
/**
* Gets current throttling settings and metrics
* @returns Current throttling configuration and metrics
*/
getThrottlingStatus(): {
enabled: boolean;
config: ThrottlingConfig;
metrics: ThrottlingMetrics;
};
/**
* Updates throttling configuration
* @param config - New throttling configuration parameters
*/
updateThrottlingConfig(config: Partial<ThrottlingConfig>): void;
/**
* Sends a message to the WebSocket server and handles the response
* @param message - The message to send
* @param timeoutMs - Optional custom timeout for this operation in milliseconds
* @returns A promise that resolves with the server response
* @throws Error if the message times out or connection fails
*/
protected sendMessage(message: Omit<HPKVRequestMessage, 'messageId'>, timeoutMs?: number): Promise<HPKVResponse>;
/**
* Processes WebSocket messages and resolves corresponding promises
* @param message - The message received from the WebSocket server
* @returns True if the message was handled, false if no matching request was found
*/
protected handleMessage(message: HPKVResponse): void;
/**
* Persistent handler for WebSocket 'error' events.
*/
protected handleWebSocketError(error: Error): void;
/**
* Persistent handler for WebSocket 'close' events.
*/
protected handleWebSocketClose(code?: number, reason?: string): void;
/**
* Builds the WebSocket connection URL with authentication
* @returns The WebSocket connection URL
*/
protected abstract buildConnectionUrl(): string;
/**
* Cleans up resources and closes the WebSocket connection.
*/
protected cleanup(code?: number, reason?: string): Promise<void>;
/**
* Handles unexpected disconnect events and decides whether to initiate reconnection.
* This method is called by handleWebSocketClose.
*/
protected initiateReconnectionCycle(): void;
/**
* Clean up resources when instance is no longer needed
*/
destroy(): void;
}
/**
* Creates a WebSocket instance that works with Node.js or browser environments
* @param url - The WebSocket URL to connect to
* @returns A WebSocket instance with normalized interface
*/
declare function createWebSocket(url: string): IWebSocket;
declare class HPKVError extends Error {
code?: number | undefined;
constructor(message: string, code?: number | undefined);
}
declare class ConnectionError extends HPKVError {
constructor(message: string);
}
declare class TimeoutError extends HPKVError {
constructor(message: string);
}
declare class AuthenticationError extends HPKVError {
constructor(message: string);
}
/**
* Client for performing CRUD operations on the key-value store
* Uses API key authentication for secure access to the HPKV API
*/
declare class HPKVApiClient extends BaseWebSocketClient {
private readonly apiKey;
/**
* Creates a new HPKVApiClient instance
* @param apiKey - The API key to use for authentication
* @param baseUrl - The base URL of the HPKV API
* @param config - The configuration for the client
*/
constructor(apiKey: string, baseUrl: string, config?: ConnectionConfig);
/**
* Builds the WebSocket connection URL with API key authentication
* @returns The WebSocket connection URL with the API key as a query parameter
*/
protected buildConnectionUrl(): string;
}
/**
* Client for subscribing to real-time updates on key changes
* This client uses a token for authentication and manages subscriptions
* to specific keys, invoking callbacks when changes occur.
*/
declare class HPKVSubscriptionClient extends BaseWebSocketClient {
private readonly token;
private subscriptions;
/**
* Creates a new HPKVSubscriptionClient instance
* @param token - The authentication token to use for WebSocket connections
* @param baseUrl - The base URL of the HPKV API
* @param config - The connection configuration
*/
constructor(token: string, baseUrl: string, config?: ConnectionConfig);
/**
* Builds the WebSocket connection URL with token-based authentication
* @returns The WebSocket connection URL with the token as a query parameter
*/
protected buildConnectionUrl(): string;
/**
* Subscribes to changes for subscribedKeys
* When changes to the key occur, the provided callback will be invoked
* with the update data
*
* @param callback - Function to be called when the key changes
* @returns The callback ID
*/
subscribe(callback: HPKVEventHandler): string;
/**
* Unsubscribes a callback from the subscription client
*
* @param callbackId - The callback ID to unsubscribe
*/
unsubscribe(callbackId: string): void;
/**
* Processes WebSocket messages and triggers subscription callbacks
* Extends the base class implementation to handle subscription events
*
* @param message - The message received from the WebSocket server
*/
protected handleMessage(message: HPKVResponse): void;
}
declare class HPKVClientFactory {
/**
* Creates a client for server-side operations using an API key
*/
static createApiClient(apiKey: string, baseUrl: string, config?: ConnectionConfig): HPKVApiClient;
/**
* Creates a client for subscription-based operations using a token
*/
static createSubscriptionClient(token: string, baseUrl: string, config?: ConnectionConfig): HPKVSubscriptionClient;
}
/**
* WebsocketTokenManager
*
* Manages authentication tokens for WebSocket connections.
* Handles token generation and API authentication.
*/
declare class WebsocketTokenManager {
private apiKey;
private baseUrl;
private fetchFn;
constructor(apiKey: string, baseUrl: string);
/**
* Generates an authentication token for WebSocket connections
*
* @param config - Configuration for the token including subscribed keys and permissions
* @returns A Promise that resolves to the generated token string
* @throws {AuthenticationError} If authentication fails
* @throws {HPKVError} If token generation fails for other reasons
*/
generateToken(config: HPKVTokenConfig): Promise<string>;
}
export { AuthenticationError, BaseWebSocketClient, ConnectionError, ConnectionState, HPKVApiClient, HPKVClientFactory, HPKVError, HPKVOperation, HPKVSubscriptionClient, MessageHandler, ThrottlingManager, TimeoutError, WS_CONSTANTS, WebsocketTokenManager, createWebSocket };
export type { ConnectionConfig, ConnectionStats, HPKVAtomicResponse, HPKVBaseResponse, HPKVDeleteResponse, HPKVErrorResponse, HPKVEventHandler, HPKVGetResponse, HPKVNotificationResponse, HPKVPatchResponse, HPKVRangeResponse, HPKVRequestMessage, HPKVResponse, HPKVSetResponse, HPKVTokenConfig, IWebSocket, PendingRequest, RangeQueryOptions, RetryConfig, ThrottlingConfig, ThrottlingMetrics };