@topgunbuild/client 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { LWWRecord, ORMapRecord, PredicateNode, LWWMap, ORMap, Timestamp, HLC } from '@topgunbuild/core';
1
+ import { LWWRecord, ORMapRecord, PredicateNode, LWWMap, ORMap, Timestamp, HLC, NodeHealth, ConnectionPoolConfig, PartitionRouterConfig, PartitionMap, ClusterClientConfig } from '@topgunbuild/core';
2
2
  export { LWWMap, LWWRecord, PredicateNode, Predicates } from '@topgunbuild/core';
3
3
  import pino from 'pino';
4
4
 
@@ -304,6 +304,104 @@ interface OperationDroppedEvent {
304
304
  key: string;
305
305
  }
306
306
 
307
+ /**
308
+ * Connection Provider Types
309
+ *
310
+ * IConnectionProvider abstracts WebSocket connection handling to support
311
+ * both single-server and cluster modes.
312
+ */
313
+ /**
314
+ * Events emitted by IConnectionProvider.
315
+ */
316
+ type ConnectionProviderEvent = 'connected' | 'disconnected' | 'reconnected' | 'message' | 'partitionMapUpdated' | 'error';
317
+ /**
318
+ * Connection event handler type.
319
+ */
320
+ type ConnectionEventHandler = (...args: any[]) => void;
321
+ /**
322
+ * Abstract interface for WebSocket connection providers.
323
+ *
324
+ * Implementations:
325
+ * - SingleServerProvider: Direct connection to a single server
326
+ * - ClusterClient: Multi-node connection pool with partition routing
327
+ */
328
+ interface IConnectionProvider {
329
+ /**
330
+ * Connect to the server(s).
331
+ * In cluster mode, connects to all seed nodes.
332
+ */
333
+ connect(): Promise<void>;
334
+ /**
335
+ * Get connection for a specific key.
336
+ * In cluster mode: routes to partition owner based on key hash.
337
+ * In single-server mode: returns the only connection.
338
+ *
339
+ * @param key - The key to route (used for partition-aware routing)
340
+ * @throws Error if not connected
341
+ */
342
+ getConnection(key: string): WebSocket;
343
+ /**
344
+ * Get any available connection.
345
+ * Used for subscriptions, metadata requests, and non-key-specific operations.
346
+ *
347
+ * @throws Error if not connected
348
+ */
349
+ getAnyConnection(): WebSocket;
350
+ /**
351
+ * Check if at least one connection is active and ready.
352
+ */
353
+ isConnected(): boolean;
354
+ /**
355
+ * Get all connected node IDs.
356
+ * Single-server mode returns ['default'].
357
+ * Cluster mode returns actual node IDs.
358
+ */
359
+ getConnectedNodes(): string[];
360
+ /**
361
+ * Subscribe to connection events.
362
+ *
363
+ * Events:
364
+ * - 'connected': A connection was established (nodeId?: string)
365
+ * - 'disconnected': A connection was lost (nodeId?: string)
366
+ * - 'reconnected': A connection was re-established after disconnect (nodeId?: string)
367
+ * - 'message': A message was received (nodeId: string, data: any)
368
+ * - 'partitionMapUpdated': Partition map was updated (cluster mode only)
369
+ * - 'error': An error occurred (error: Error)
370
+ */
371
+ on(event: ConnectionProviderEvent, handler: ConnectionEventHandler): void;
372
+ /**
373
+ * Unsubscribe from connection events.
374
+ */
375
+ off(event: ConnectionProviderEvent, handler: ConnectionEventHandler): void;
376
+ /**
377
+ * Send a message via the appropriate connection.
378
+ * In cluster mode, routes based on key if provided.
379
+ *
380
+ * @param data - Serialized message data
381
+ * @param key - Optional key for routing (cluster mode)
382
+ */
383
+ send(data: ArrayBuffer | Uint8Array, key?: string): void;
384
+ /**
385
+ * Close all connections gracefully.
386
+ */
387
+ close(): Promise<void>;
388
+ }
389
+ /**
390
+ * Configuration for SingleServerProvider.
391
+ */
392
+ interface SingleServerProviderConfig {
393
+ /** WebSocket URL to connect to */
394
+ url: string;
395
+ /** Maximum reconnection attempts (default: 10) */
396
+ maxReconnectAttempts?: number;
397
+ /** Initial reconnect delay in ms (default: 1000) */
398
+ reconnectDelayMs?: number;
399
+ /** Backoff multiplier for reconnect delay (default: 2) */
400
+ backoffMultiplier?: number;
401
+ /** Maximum reconnect delay in ms (default: 30000) */
402
+ maxReconnectDelayMs?: number;
403
+ }
404
+
307
405
  interface HeartbeatConfig {
308
406
  intervalMs: number;
309
407
  timeoutMs: number;
@@ -323,7 +421,10 @@ interface BackoffConfig {
323
421
  }
324
422
  interface SyncEngineConfig {
325
423
  nodeId: string;
326
- serverUrl: string;
424
+ /** @deprecated Use connectionProvider instead */
425
+ serverUrl?: string;
426
+ /** Connection provider (preferred over serverUrl) */
427
+ connectionProvider?: IConnectionProvider;
327
428
  storageAdapter: IStorageAdapter;
328
429
  reconnectInterval?: number;
329
430
  heartbeat?: Partial<HeartbeatConfig>;
@@ -337,6 +438,8 @@ declare class SyncEngine {
337
438
  private readonly hlc;
338
439
  private readonly stateMachine;
339
440
  private readonly backoffConfig;
441
+ private readonly connectionProvider;
442
+ private readonly useConnectionProvider;
340
443
  private websocket;
341
444
  private opLog;
342
445
  private maps;
@@ -357,6 +460,7 @@ declare class SyncEngine {
357
460
  private waitingForCapacity;
358
461
  private highWaterMarkEmitted;
359
462
  private backpressureListeners;
463
+ private pendingWriteConcernPromises;
360
464
  constructor(config: SyncEngineConfig);
361
465
  /**
362
466
  * Get the current connection state
@@ -383,6 +487,14 @@ declare class SyncEngine {
383
487
  * Check if fully connected and synced
384
488
  */
385
489
  private isConnected;
490
+ /**
491
+ * Initialize connection using IConnectionProvider (Phase 4.5 cluster mode).
492
+ * Sets up event handlers for the connection provider.
493
+ */
494
+ private initConnectionProvider;
495
+ /**
496
+ * Initialize connection using direct WebSocket (legacy single-server mode).
497
+ */
386
498
  private initConnection;
387
499
  private scheduleReconnect;
388
500
  private calculateBackoffDelay;
@@ -390,6 +502,18 @@ declare class SyncEngine {
390
502
  * Reset backoff counter (called on successful connection)
391
503
  */
392
504
  private resetBackoff;
505
+ /**
506
+ * Send a message through the current connection.
507
+ * Uses connectionProvider if in cluster mode, otherwise uses direct websocket.
508
+ * @param message Message object to serialize and send
509
+ * @param key Optional key for routing (cluster mode only)
510
+ * @returns true if message was sent, false otherwise
511
+ */
512
+ private sendMessage;
513
+ /**
514
+ * Check if we can send messages (connection is ready).
515
+ */
516
+ private canSend;
393
517
  private loadOpLog;
394
518
  private saveOpLog;
395
519
  registerMap(mapName: string, map: LWWMap<any, any> | ORMap<any, any>): void;
@@ -424,6 +548,11 @@ declare class SyncEngine {
424
548
  releaseLock(name: string, requestId: string, fencingToken: number): Promise<boolean>;
425
549
  private handleServerMessage;
426
550
  getHLC(): HLC;
551
+ /**
552
+ * Helper method to apply a single server event to the local map.
553
+ * Used by both SERVER_EVENT and SERVER_BATCH_EVENT handlers.
554
+ */
555
+ private applyServerEvent;
427
556
  /**
428
557
  * Closes the WebSocket connection and cleans up resources.
429
558
  */
@@ -433,6 +562,43 @@ declare class SyncEngine {
433
562
  * Use after fatal errors to start fresh.
434
563
  */
435
564
  resetConnection(): void;
565
+ /**
566
+ * Wait for a partition map update from the connection provider.
567
+ * Used when an operation fails with NOT_OWNER error and needs
568
+ * to wait for an updated partition map before retrying.
569
+ *
570
+ * @param timeoutMs - Maximum time to wait (default: 5000ms)
571
+ * @returns Promise that resolves when partition map is updated or times out
572
+ */
573
+ waitForPartitionMapUpdate(timeoutMs?: number): Promise<void>;
574
+ /**
575
+ * Wait for the connection to be available.
576
+ * Used when an operation fails due to connection issues and needs
577
+ * to wait for reconnection before retrying.
578
+ *
579
+ * @param timeoutMs - Maximum time to wait (default: 10000ms)
580
+ * @returns Promise that resolves when connected or rejects on timeout
581
+ */
582
+ waitForConnection(timeoutMs?: number): Promise<void>;
583
+ /**
584
+ * Wait for a specific sync state.
585
+ * Useful for waiting until fully connected and synced.
586
+ *
587
+ * @param targetState - The state to wait for
588
+ * @param timeoutMs - Maximum time to wait (default: 30000ms)
589
+ * @returns Promise that resolves when state is reached or rejects on timeout
590
+ */
591
+ waitForState(targetState: SyncState, timeoutMs?: number): Promise<void>;
592
+ /**
593
+ * Check if the connection provider is connected.
594
+ * Convenience method for failover logic.
595
+ */
596
+ isProviderConnected(): boolean;
597
+ /**
598
+ * Get the connection provider for direct access.
599
+ * Use with caution - prefer using SyncEngine methods.
600
+ */
601
+ getConnectionProvider(): IConnectionProvider;
436
602
  private resetMap;
437
603
  /**
438
604
  * Starts the heartbeat mechanism after successful connection.
@@ -514,6 +680,26 @@ declare class SyncEngine {
514
680
  * Drop the oldest pending operation (used by 'drop-oldest' strategy).
515
681
  */
516
682
  private dropOldestOp;
683
+ /**
684
+ * Register a pending Write Concern promise for an operation.
685
+ * The promise will be resolved when the server sends an ACK with the operation result.
686
+ *
687
+ * @param opId - Operation ID
688
+ * @param timeout - Timeout in ms (default: 5000)
689
+ * @returns Promise that resolves with the Write Concern result
690
+ */
691
+ registerWriteConcernPromise(opId: string, timeout?: number): Promise<any>;
692
+ /**
693
+ * Resolve a pending Write Concern promise with the server result.
694
+ *
695
+ * @param opId - Operation ID
696
+ * @param result - Result from server ACK
697
+ */
698
+ private resolveWriteConcernPromise;
699
+ /**
700
+ * Cancel all pending Write Concern promises (e.g., on disconnect).
701
+ */
702
+ private cancelAllWriteConcernPromises;
517
703
  }
518
704
 
519
705
  interface ILock {
@@ -532,19 +718,55 @@ declare class DistributedLock implements ILock {
532
718
  isLocked(): boolean;
533
719
  }
534
720
 
721
+ /**
722
+ * Cluster mode configuration for TopGunClient.
723
+ * When provided, the client connects to multiple nodes with partition-aware routing.
724
+ */
725
+ interface TopGunClusterConfig {
726
+ /** Initial seed nodes (at least one required) */
727
+ seeds: string[];
728
+ /** Connection pool size per node (default: 1) */
729
+ connectionsPerNode?: number;
730
+ /** Enable smart routing to partition owner (default: true) */
731
+ smartRouting?: boolean;
732
+ /** Partition map refresh interval in ms (default: 30000) */
733
+ partitionMapRefreshMs?: number;
734
+ /** Connection timeout per node in ms (default: 5000) */
735
+ connectionTimeoutMs?: number;
736
+ /** Retry attempts for failed operations (default: 3) */
737
+ retryAttempts?: number;
738
+ }
739
+ /**
740
+ * Default values for cluster configuration
741
+ */
742
+ declare const DEFAULT_CLUSTER_CONFIG: Required<Omit<TopGunClusterConfig, 'seeds'>>;
743
+ /**
744
+ * TopGunClient configuration options
745
+ */
746
+ interface TopGunClientConfig {
747
+ /** Unique node identifier (auto-generated if not provided) */
748
+ nodeId?: string;
749
+ /** Single-server mode: WebSocket URL to connect to */
750
+ serverUrl?: string;
751
+ /** Cluster mode: Configuration for multi-node routing */
752
+ cluster?: TopGunClusterConfig;
753
+ /** Storage adapter for local persistence */
754
+ storage: IStorageAdapter;
755
+ /** Backoff configuration for reconnection */
756
+ backoff?: Partial<BackoffConfig>;
757
+ /** Backpressure configuration */
758
+ backpressure?: Partial<BackpressureConfig>;
759
+ }
535
760
  declare class TopGunClient {
536
761
  private readonly nodeId;
537
762
  private readonly syncEngine;
538
763
  private readonly maps;
539
764
  private readonly storageAdapter;
540
765
  private readonly topicHandles;
541
- constructor(config: {
542
- nodeId?: string;
543
- serverUrl: string;
544
- storage: IStorageAdapter;
545
- backoff?: Partial<BackoffConfig>;
546
- backpressure?: Partial<BackpressureConfig>;
547
- });
766
+ private readonly clusterClient?;
767
+ private readonly isClusterMode;
768
+ private readonly clusterConfig?;
769
+ constructor(config: TopGunClientConfig);
548
770
  start(): Promise<void>;
549
771
  setAuthToken(token: string): void;
550
772
  setAuthTokenProvider(provider: () => Promise<string | null>): void;
@@ -581,6 +803,46 @@ declare class TopGunClient {
581
803
  * Closes the client, disconnecting from the server and cleaning up resources.
582
804
  */
583
805
  close(): void;
806
+ /**
807
+ * Check if running in cluster mode
808
+ */
809
+ isCluster(): boolean;
810
+ /**
811
+ * Get list of connected cluster nodes (cluster mode only)
812
+ * @returns Array of connected node IDs, or empty array in single-server mode
813
+ */
814
+ getConnectedNodes(): string[];
815
+ /**
816
+ * Get the current partition map version (cluster mode only)
817
+ * @returns Partition map version, or 0 in single-server mode
818
+ */
819
+ getPartitionMapVersion(): number;
820
+ /**
821
+ * Check if direct routing is active (cluster mode only)
822
+ * Direct routing sends operations directly to partition owners.
823
+ * @returns true if routing is active, false otherwise
824
+ */
825
+ isRoutingActive(): boolean;
826
+ /**
827
+ * Get health status for all cluster nodes (cluster mode only)
828
+ * @returns Map of node IDs to their health status
829
+ */
830
+ getClusterHealth(): Map<string, NodeHealth>;
831
+ /**
832
+ * Force refresh of partition map (cluster mode only)
833
+ * Use this after detecting routing errors.
834
+ */
835
+ refreshPartitionMap(): Promise<void>;
836
+ /**
837
+ * Get cluster router statistics (cluster mode only)
838
+ */
839
+ getClusterStats(): {
840
+ mapVersion: number;
841
+ partitionCount: number;
842
+ nodeCount: number;
843
+ lastRefresh: number;
844
+ isStale: boolean;
845
+ } | null;
584
846
  /**
585
847
  * Get the current connection state
586
848
  */
@@ -771,6 +1033,574 @@ declare class BackpressureError extends Error {
771
1033
  constructor(pendingCount: number, maxPending: number);
772
1034
  }
773
1035
 
1036
+ /**
1037
+ * ConnectionPool - Manages WebSocket connections to multiple cluster nodes
1038
+ *
1039
+ * Phase 4: Partition-Aware Client Routing
1040
+ *
1041
+ * Features:
1042
+ * - Maintains connections to all known cluster nodes
1043
+ * - Automatic reconnection with exponential backoff
1044
+ * - Health monitoring and status tracking
1045
+ * - Connection lifecycle management
1046
+ */
1047
+
1048
+ interface ConnectionPoolEvents {
1049
+ 'node:connected': (nodeId: string) => void;
1050
+ 'node:disconnected': (nodeId: string, reason: string) => void;
1051
+ 'node:healthy': (nodeId: string) => void;
1052
+ 'node:unhealthy': (nodeId: string, reason: string) => void;
1053
+ 'message': (nodeId: string, message: any) => void;
1054
+ 'error': (nodeId: string, error: Error) => void;
1055
+ }
1056
+ declare class ConnectionPool {
1057
+ private readonly listeners;
1058
+ private readonly config;
1059
+ private readonly connections;
1060
+ private primaryNodeId;
1061
+ private healthCheckTimer;
1062
+ private authToken;
1063
+ constructor(config?: Partial<ConnectionPoolConfig>);
1064
+ on(event: string, listener: (...args: any[]) => void): this;
1065
+ off(event: string, listener: (...args: any[]) => void): this;
1066
+ emit(event: string, ...args: any[]): boolean;
1067
+ removeAllListeners(event?: string): this;
1068
+ /**
1069
+ * Set authentication token for all connections
1070
+ */
1071
+ setAuthToken(token: string): void;
1072
+ /**
1073
+ * Add a node to the connection pool
1074
+ */
1075
+ addNode(nodeId: string, endpoint: string): Promise<void>;
1076
+ /**
1077
+ * Remove a node from the connection pool
1078
+ */
1079
+ removeNode(nodeId: string): Promise<void>;
1080
+ /**
1081
+ * Get connection for a specific node
1082
+ */
1083
+ getConnection(nodeId: string): WebSocket | null;
1084
+ /**
1085
+ * Get primary connection (first/seed node)
1086
+ */
1087
+ getPrimaryConnection(): WebSocket | null;
1088
+ /**
1089
+ * Get any healthy connection
1090
+ */
1091
+ getAnyHealthyConnection(): {
1092
+ nodeId: string;
1093
+ socket: WebSocket;
1094
+ } | null;
1095
+ /**
1096
+ * Send message to a specific node
1097
+ */
1098
+ send(nodeId: string, message: any): boolean;
1099
+ /**
1100
+ * Send message to primary node
1101
+ */
1102
+ sendToPrimary(message: any): boolean;
1103
+ /**
1104
+ * Get health status for all nodes
1105
+ */
1106
+ getHealthStatus(): Map<string, NodeHealth>;
1107
+ /**
1108
+ * Get list of connected node IDs
1109
+ */
1110
+ getConnectedNodes(): string[];
1111
+ /**
1112
+ * Get all node IDs
1113
+ */
1114
+ getAllNodes(): string[];
1115
+ /**
1116
+ * Check if node is connected and authenticated
1117
+ */
1118
+ isNodeConnected(nodeId: string): boolean;
1119
+ /**
1120
+ * Check if connected to a specific node.
1121
+ * Alias for isNodeConnected() for IConnectionProvider compatibility.
1122
+ */
1123
+ isConnected(nodeId: string): boolean;
1124
+ /**
1125
+ * Start health monitoring
1126
+ */
1127
+ startHealthCheck(): void;
1128
+ /**
1129
+ * Stop health monitoring
1130
+ */
1131
+ stopHealthCheck(): void;
1132
+ /**
1133
+ * Close all connections and cleanup
1134
+ */
1135
+ close(): void;
1136
+ private connect;
1137
+ private sendAuth;
1138
+ private handleMessage;
1139
+ private flushPendingMessages;
1140
+ private scheduleReconnect;
1141
+ private performHealthCheck;
1142
+ }
1143
+
1144
+ /**
1145
+ * PartitionRouter - Routes operations to the correct cluster node
1146
+ *
1147
+ * Phase 4: Partition-Aware Client Routing
1148
+ *
1149
+ * Features:
1150
+ * - Maintains local copy of partition map
1151
+ * - Routes keys to owner nodes using consistent hashing
1152
+ * - Handles stale routing with automatic refresh
1153
+ * - Supports fallback to server-side forwarding
1154
+ */
1155
+
1156
+ interface RoutingResult {
1157
+ nodeId: string;
1158
+ partitionId: number;
1159
+ isOwner: boolean;
1160
+ isBackup: boolean;
1161
+ }
1162
+ interface PartitionRouterEvents {
1163
+ 'partitionMap:updated': (version: number, changesCount: number) => void;
1164
+ 'partitionMap:stale': (currentVersion: number, lastRefresh: number) => void;
1165
+ 'routing:miss': (key: string, expectedOwner: string, actualOwner: string) => void;
1166
+ }
1167
+ declare class PartitionRouter {
1168
+ private readonly listeners;
1169
+ private readonly config;
1170
+ private readonly connectionPool;
1171
+ private partitionMap;
1172
+ private lastRefreshTime;
1173
+ private refreshTimer;
1174
+ private pendingRefresh;
1175
+ constructor(connectionPool: ConnectionPool, config?: Partial<PartitionRouterConfig>);
1176
+ on(event: string, listener: (...args: any[]) => void): this;
1177
+ off(event: string, listener: (...args: any[]) => void): this;
1178
+ once(event: string, listener: (...args: any[]) => void): this;
1179
+ emit(event: string, ...args: any[]): boolean;
1180
+ removeListener(event: string, listener: (...args: any[]) => void): this;
1181
+ removeAllListeners(event?: string): this;
1182
+ /**
1183
+ * Get the partition ID for a given key
1184
+ */
1185
+ getPartitionId(key: string): number;
1186
+ /**
1187
+ * Route a key to the owner node
1188
+ */
1189
+ route(key: string): RoutingResult | null;
1190
+ /**
1191
+ * Route a key and get the WebSocket connection to use
1192
+ */
1193
+ routeToConnection(key: string): {
1194
+ nodeId: string;
1195
+ socket: WebSocket;
1196
+ } | null;
1197
+ /**
1198
+ * Get routing info for multiple keys (batch routing)
1199
+ */
1200
+ routeBatch(keys: string[]): Map<string, RoutingResult[]>;
1201
+ /**
1202
+ * Get all partitions owned by a specific node
1203
+ */
1204
+ getPartitionsForNode(nodeId: string): number[];
1205
+ /**
1206
+ * Get current partition map version
1207
+ */
1208
+ getMapVersion(): number;
1209
+ /**
1210
+ * Check if partition map is available
1211
+ */
1212
+ hasPartitionMap(): boolean;
1213
+ /**
1214
+ * Get owner node for a key.
1215
+ * Returns null if partition map is not available.
1216
+ */
1217
+ getOwner(key: string): string | null;
1218
+ /**
1219
+ * Get backup nodes for a key.
1220
+ * Returns empty array if partition map is not available.
1221
+ */
1222
+ getBackups(key: string): string[];
1223
+ /**
1224
+ * Get the full partition map.
1225
+ * Returns null if not available.
1226
+ */
1227
+ getMap(): PartitionMap | null;
1228
+ /**
1229
+ * Update entire partition map.
1230
+ * Only accepts newer versions.
1231
+ */
1232
+ updateMap(map: PartitionMap): boolean;
1233
+ /**
1234
+ * Update a single partition (for delta updates).
1235
+ */
1236
+ updatePartition(partitionId: number, owner: string, backups: string[]): void;
1237
+ /**
1238
+ * Check if partition map is stale
1239
+ */
1240
+ isMapStale(): boolean;
1241
+ /**
1242
+ * Request fresh partition map from server
1243
+ */
1244
+ refreshPartitionMap(): Promise<void>;
1245
+ /**
1246
+ * Start periodic partition map refresh
1247
+ */
1248
+ startPeriodicRefresh(): void;
1249
+ /**
1250
+ * Stop periodic refresh
1251
+ */
1252
+ stopPeriodicRefresh(): void;
1253
+ /**
1254
+ * Handle NOT_OWNER error from server
1255
+ */
1256
+ handleNotOwnerError(key: string, actualOwner: string, newMapVersion: number): void;
1257
+ /**
1258
+ * Get statistics about routing
1259
+ */
1260
+ getStats(): {
1261
+ mapVersion: number;
1262
+ partitionCount: number;
1263
+ nodeCount: number;
1264
+ lastRefresh: number;
1265
+ isStale: boolean;
1266
+ };
1267
+ /**
1268
+ * Cleanup resources
1269
+ */
1270
+ close(): void;
1271
+ private handlePartitionMap;
1272
+ private handlePartitionMapDelta;
1273
+ private applyPartitionChange;
1274
+ private updateConnectionPool;
1275
+ private doRefreshPartitionMap;
1276
+ }
1277
+
1278
+ /**
1279
+ * ClusterClient - Cluster-aware client wrapper
1280
+ *
1281
+ * Phase 4: Partition-Aware Client Routing
1282
+ * Phase 4.5: Implements IConnectionProvider for SyncEngine abstraction
1283
+ *
1284
+ * Wraps the standard TopGunClient with cluster-aware routing capabilities.
1285
+ * Coordinates between ConnectionPool and PartitionRouter for optimal
1286
+ * request routing in a clustered environment.
1287
+ */
1288
+
1289
+ interface ClusterClientEvents {
1290
+ 'connected': () => void;
1291
+ 'disconnected': (reason: string) => void;
1292
+ 'partitionMap:ready': (version: number) => void;
1293
+ 'routing:active': () => void;
1294
+ 'error': (error: Error) => void;
1295
+ 'circuit:open': (nodeId: string) => void;
1296
+ 'circuit:closed': (nodeId: string) => void;
1297
+ 'circuit:half-open': (nodeId: string) => void;
1298
+ }
1299
+ /**
1300
+ * Circuit breaker state for a node.
1301
+ */
1302
+ interface CircuitState {
1303
+ /** Number of consecutive failures */
1304
+ failures: number;
1305
+ /** Timestamp of last failure */
1306
+ lastFailure: number;
1307
+ /** Current circuit state */
1308
+ state: 'closed' | 'open' | 'half-open';
1309
+ }
1310
+ /**
1311
+ * Routing metrics for monitoring smart routing effectiveness.
1312
+ */
1313
+ interface RoutingMetrics {
1314
+ /** Operations routed directly to partition owner */
1315
+ directRoutes: number;
1316
+ /** Operations falling back to any node (owner unavailable) */
1317
+ fallbackRoutes: number;
1318
+ /** Operations when partition map is missing/stale */
1319
+ partitionMisses: number;
1320
+ /** Total routing decisions made */
1321
+ totalRoutes: number;
1322
+ }
1323
+ type ClusterRoutingMode = 'direct' | 'forward';
1324
+ /**
1325
+ * ClusterClient implements IConnectionProvider for multi-node cluster mode.
1326
+ * It provides partition-aware routing and connection management.
1327
+ */
1328
+ declare class ClusterClient implements IConnectionProvider {
1329
+ private readonly listeners;
1330
+ private readonly connectionPool;
1331
+ private readonly partitionRouter;
1332
+ private readonly config;
1333
+ private initialized;
1334
+ private routingActive;
1335
+ private readonly routingMetrics;
1336
+ private readonly circuits;
1337
+ private readonly circuitBreakerConfig;
1338
+ constructor(config: ClusterClientConfig);
1339
+ on(event: string, listener: (...args: any[]) => void): this;
1340
+ off(event: string, listener: (...args: any[]) => void): this;
1341
+ emit(event: string, ...args: any[]): boolean;
1342
+ removeAllListeners(event?: string): this;
1343
+ /**
1344
+ * Connect to cluster nodes (IConnectionProvider interface).
1345
+ * Alias for start() method.
1346
+ */
1347
+ connect(): Promise<void>;
1348
+ /**
1349
+ * Get connection for a specific key (IConnectionProvider interface).
1350
+ * Routes to partition owner based on key hash when smart routing is enabled.
1351
+ * @throws Error if not connected
1352
+ */
1353
+ getConnection(key: string): WebSocket;
1354
+ /**
1355
+ * Get fallback connection when owner is unavailable.
1356
+ * @throws Error if no connection available
1357
+ */
1358
+ private getFallbackConnection;
1359
+ /**
1360
+ * Request a partition map refresh in the background.
1361
+ * Called when routing to an unknown/disconnected owner.
1362
+ */
1363
+ private requestPartitionMapRefresh;
1364
+ /**
1365
+ * Request partition map from a specific node.
1366
+ * Called on first node connection.
1367
+ */
1368
+ private requestPartitionMapFromNode;
1369
+ /**
1370
+ * Check if at least one connection is active (IConnectionProvider interface).
1371
+ */
1372
+ isConnected(): boolean;
1373
+ /**
1374
+ * Send data via the appropriate connection (IConnectionProvider interface).
1375
+ * Routes based on key if provided.
1376
+ */
1377
+ send(data: ArrayBuffer | Uint8Array, key?: string): void;
1378
+ /**
1379
+ * Send data with automatic retry and rerouting on failure.
1380
+ * @param data - Data to send
1381
+ * @param key - Optional key for routing
1382
+ * @param options - Retry options
1383
+ * @throws Error after max retries exceeded
1384
+ */
1385
+ sendWithRetry(data: ArrayBuffer | Uint8Array, key?: string, options?: {
1386
+ maxRetries?: number;
1387
+ retryDelayMs?: number;
1388
+ retryOnNotOwner?: boolean;
1389
+ }): Promise<void>;
1390
+ /**
1391
+ * Check if an error is retryable.
1392
+ */
1393
+ private isRetryableError;
1394
+ /**
1395
+ * Wait for partition map update.
1396
+ */
1397
+ private waitForPartitionMapUpdateInternal;
1398
+ /**
1399
+ * Wait for at least one connection to be available.
1400
+ */
1401
+ private waitForConnectionInternal;
1402
+ /**
1403
+ * Helper delay function.
1404
+ */
1405
+ private delay;
1406
+ /**
1407
+ * Initialize cluster connections
1408
+ */
1409
+ start(): Promise<void>;
1410
+ /**
1411
+ * Set authentication token
1412
+ */
1413
+ setAuthToken(token: string): void;
1414
+ /**
1415
+ * Send operation with automatic routing (legacy API for cluster operations).
1416
+ * @deprecated Use send(data, key) for IConnectionProvider interface
1417
+ */
1418
+ sendMessage(key: string, message: any): boolean;
1419
+ /**
1420
+ * Send directly to partition owner
1421
+ */
1422
+ sendDirect(key: string, message: any): boolean;
1423
+ /**
1424
+ * Send to primary node for server-side forwarding
1425
+ */
1426
+ sendForward(message: any): boolean;
1427
+ /**
1428
+ * Send batch of operations with routing
1429
+ */
1430
+ sendBatch(operations: Array<{
1431
+ key: string;
1432
+ message: any;
1433
+ }>): Map<string, boolean>;
1434
+ /**
1435
+ * Get connection pool health status
1436
+ */
1437
+ getHealthStatus(): Map<string, NodeHealth>;
1438
+ /**
1439
+ * Get partition router stats
1440
+ */
1441
+ getRouterStats(): ReturnType<PartitionRouter['getStats']>;
1442
+ /**
1443
+ * Get routing metrics for monitoring smart routing effectiveness.
1444
+ */
1445
+ getRoutingMetrics(): RoutingMetrics;
1446
+ /**
1447
+ * Reset routing metrics counters.
1448
+ * Useful for monitoring intervals.
1449
+ */
1450
+ resetRoutingMetrics(): void;
1451
+ /**
1452
+ * Check if cluster routing is active
1453
+ */
1454
+ isRoutingActive(): boolean;
1455
+ /**
1456
+ * Get list of connected nodes
1457
+ */
1458
+ getConnectedNodes(): string[];
1459
+ /**
1460
+ * Check if cluster client is initialized
1461
+ */
1462
+ isInitialized(): boolean;
1463
+ /**
1464
+ * Force refresh of partition map
1465
+ */
1466
+ refreshPartitionMap(): Promise<void>;
1467
+ /**
1468
+ * Shutdown cluster client (IConnectionProvider interface).
1469
+ */
1470
+ close(): Promise<void>;
1471
+ /**
1472
+ * Get the connection pool (for internal use)
1473
+ */
1474
+ getConnectionPool(): ConnectionPool;
1475
+ /**
1476
+ * Get the partition router (for internal use)
1477
+ */
1478
+ getPartitionRouter(): PartitionRouter;
1479
+ /**
1480
+ * Get any healthy WebSocket connection (IConnectionProvider interface).
1481
+ * @throws Error if not connected
1482
+ */
1483
+ getAnyConnection(): WebSocket;
1484
+ /**
1485
+ * Get any healthy WebSocket connection, or null if none available.
1486
+ * Use this for optional connection checks.
1487
+ */
1488
+ getAnyConnectionOrNull(): WebSocket | null;
1489
+ /**
1490
+ * Get circuit breaker state for a node.
1491
+ */
1492
+ getCircuit(nodeId: string): CircuitState;
1493
+ /**
1494
+ * Check if a node can be used (circuit not open).
1495
+ */
1496
+ canUseNode(nodeId: string): boolean;
1497
+ /**
1498
+ * Record a successful operation to a node.
1499
+ * Resets circuit breaker on success.
1500
+ */
1501
+ recordSuccess(nodeId: string): void;
1502
+ /**
1503
+ * Record a failed operation to a node.
1504
+ * Opens circuit breaker after threshold failures.
1505
+ */
1506
+ recordFailure(nodeId: string): void;
1507
+ /**
1508
+ * Get all circuit breaker states.
1509
+ */
1510
+ getCircuitStates(): Map<string, CircuitState>;
1511
+ /**
1512
+ * Reset circuit breaker for a specific node.
1513
+ */
1514
+ resetCircuit(nodeId: string): void;
1515
+ /**
1516
+ * Reset all circuit breakers.
1517
+ */
1518
+ resetAllCircuits(): void;
1519
+ private setupEventHandlers;
1520
+ private waitForPartitionMap;
1521
+ }
1522
+
1523
+ /**
1524
+ * SingleServerProvider implements IConnectionProvider for single-server mode.
1525
+ *
1526
+ * This is an adapter that wraps direct WebSocket connection handling,
1527
+ * providing the same interface used by ClusterClient for multi-node mode.
1528
+ */
1529
+ declare class SingleServerProvider implements IConnectionProvider {
1530
+ private readonly url;
1531
+ private readonly config;
1532
+ private ws;
1533
+ private reconnectAttempts;
1534
+ private reconnectTimer;
1535
+ private isClosing;
1536
+ private listeners;
1537
+ constructor(config: SingleServerProviderConfig);
1538
+ /**
1539
+ * Connect to the WebSocket server.
1540
+ */
1541
+ connect(): Promise<void>;
1542
+ /**
1543
+ * Get connection for a specific key.
1544
+ * In single-server mode, key is ignored.
1545
+ */
1546
+ getConnection(_key: string): WebSocket;
1547
+ /**
1548
+ * Get any available connection.
1549
+ */
1550
+ getAnyConnection(): WebSocket;
1551
+ /**
1552
+ * Check if connected.
1553
+ */
1554
+ isConnected(): boolean;
1555
+ /**
1556
+ * Get connected node IDs.
1557
+ * Single-server mode returns ['default'] when connected.
1558
+ */
1559
+ getConnectedNodes(): string[];
1560
+ /**
1561
+ * Subscribe to connection events.
1562
+ */
1563
+ on(event: ConnectionProviderEvent, handler: ConnectionEventHandler): void;
1564
+ /**
1565
+ * Unsubscribe from connection events.
1566
+ */
1567
+ off(event: ConnectionProviderEvent, handler: ConnectionEventHandler): void;
1568
+ /**
1569
+ * Send data via the WebSocket connection.
1570
+ * In single-server mode, key parameter is ignored.
1571
+ */
1572
+ send(data: ArrayBuffer | Uint8Array, _key?: string): void;
1573
+ /**
1574
+ * Close the WebSocket connection.
1575
+ */
1576
+ close(): Promise<void>;
1577
+ /**
1578
+ * Emit an event to all listeners.
1579
+ */
1580
+ private emit;
1581
+ /**
1582
+ * Schedule a reconnection attempt with exponential backoff.
1583
+ */
1584
+ private scheduleReconnect;
1585
+ /**
1586
+ * Calculate backoff delay with exponential increase.
1587
+ */
1588
+ private calculateBackoffDelay;
1589
+ /**
1590
+ * Get the WebSocket URL this provider connects to.
1591
+ */
1592
+ getUrl(): string;
1593
+ /**
1594
+ * Get current reconnection attempt count.
1595
+ */
1596
+ getReconnectAttempts(): number;
1597
+ /**
1598
+ * Reset reconnection counter.
1599
+ * Called externally after successful authentication.
1600
+ */
1601
+ resetReconnectAttempts(): void;
1602
+ }
1603
+
774
1604
  declare const logger: pino.Logger<never, boolean>;
775
1605
 
776
- export { type BackoffConfig, type BackpressureConfig, BackpressureError, type BackpressureStatus, type BackpressureStrategy, type BackpressureThresholdEvent, DEFAULT_BACKPRESSURE_CONFIG, EncryptedStorageAdapter, type HeartbeatConfig, IDBAdapter, type IStorageAdapter, type OpLogEntry, type OperationDroppedEvent, type QueryFilter, QueryHandle, type QueryResultItem, type QueryResultSource, type StateChangeEvent, type StateChangeListener, SyncEngine, type SyncEngineConfig, SyncState, SyncStateMachine, type SyncStateMachineConfig, TopGun, TopGunClient, type TopicCallback, TopicHandle, VALID_TRANSITIONS, isValidTransition, logger };
1606
+ export { type BackoffConfig, type BackpressureConfig, BackpressureError, type BackpressureStatus, type BackpressureStrategy, type BackpressureThresholdEvent, type CircuitState, ClusterClient, type ClusterClientEvents, type ClusterRoutingMode, type ConnectionEventHandler, ConnectionPool, type ConnectionPoolEvents, type ConnectionProviderEvent, DEFAULT_BACKPRESSURE_CONFIG, DEFAULT_CLUSTER_CONFIG, EncryptedStorageAdapter, type HeartbeatConfig, type IConnectionProvider, IDBAdapter, type IStorageAdapter, type OpLogEntry, type OperationDroppedEvent, PartitionRouter, type PartitionRouterEvents, type QueryFilter, QueryHandle, type QueryResultItem, type QueryResultSource, type RoutingMetrics, type RoutingResult, SingleServerProvider, type SingleServerProviderConfig, type StateChangeEvent, type StateChangeListener, SyncEngine, type SyncEngineConfig, SyncState, SyncStateMachine, type SyncStateMachineConfig, TopGun, TopGunClient, type TopGunClientConfig, type TopGunClusterConfig, type TopicCallback, TopicHandle, VALID_TRANSITIONS, isValidTransition, logger };