UNPKG

@hpkv/websocket-client

Version:
697 lines (683 loc) 24 kB
/** * @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 };