@topgunbuild/client 0.2.1 → 0.4.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.mts +1518 -63
- package/dist/index.d.ts +1518 -63
- package/dist/index.js +3331 -137
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3325 -129
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -10
- package/LICENSE +0 -97
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LWWRecord, ORMapRecord, PredicateNode,
|
|
1
|
+
import { LWWRecord, ORMapRecord, PredicateNode, ConflictResolverDef, MergeRejection, Timestamp, LWWMap, ORMap, HLC, EntryProcessorDef, EntryProcessorResult, PNCounter, PNCounterState, JournalEvent, JournalEventType, 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
|
|
|
@@ -35,6 +35,65 @@ interface IStorageAdapter {
|
|
|
35
35
|
getAllKeys(): Promise<string[]>;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Represents a change event for tracking data mutations.
|
|
40
|
+
*/
|
|
41
|
+
interface ChangeEvent<T> {
|
|
42
|
+
/** Type of change: 'add' for new entries, 'update' for modified entries, 'remove' for deleted entries */
|
|
43
|
+
type: 'add' | 'update' | 'remove';
|
|
44
|
+
/** The key of the changed entry */
|
|
45
|
+
key: string;
|
|
46
|
+
/** New value (present for 'add' and 'update') */
|
|
47
|
+
value?: T;
|
|
48
|
+
/** Previous value (present for 'update' and 'remove') */
|
|
49
|
+
previousValue?: T;
|
|
50
|
+
/** HLC timestamp of the change */
|
|
51
|
+
timestamp: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* ChangeTracker computes differences between snapshots of a Map.
|
|
55
|
+
* Used to track add/update/remove changes for subscription notifications.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const tracker = new ChangeTracker<Todo>();
|
|
60
|
+
*
|
|
61
|
+
* // First snapshot
|
|
62
|
+
* const changes1 = tracker.computeChanges(
|
|
63
|
+
* new Map([['a', { title: 'Todo A' }]]),
|
|
64
|
+
* Date.now()
|
|
65
|
+
* );
|
|
66
|
+
* // changes1 = [{ type: 'add', key: 'a', value: { title: 'Todo A' }, timestamp: ... }]
|
|
67
|
+
*
|
|
68
|
+
* // Second snapshot with update
|
|
69
|
+
* const changes2 = tracker.computeChanges(
|
|
70
|
+
* new Map([['a', { title: 'Todo A Updated' }]]),
|
|
71
|
+
* Date.now()
|
|
72
|
+
* );
|
|
73
|
+
* // changes2 = [{ type: 'update', key: 'a', value: { title: 'Todo A Updated' }, previousValue: { title: 'Todo A' }, timestamp: ... }]
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
declare class ChangeTracker<T> {
|
|
77
|
+
private previousSnapshot;
|
|
78
|
+
/**
|
|
79
|
+
* Computes changes between previous and current state.
|
|
80
|
+
* Updates internal snapshot after computation.
|
|
81
|
+
*
|
|
82
|
+
* @param current - Current state as a Map
|
|
83
|
+
* @param timestamp - HLC timestamp for the changes
|
|
84
|
+
* @returns Array of change events (may be empty if no changes)
|
|
85
|
+
*/
|
|
86
|
+
computeChanges(current: Map<string, T>, timestamp: number): ChangeEvent<T>[];
|
|
87
|
+
/**
|
|
88
|
+
* Reset tracker (e.g., on query change or reconnect)
|
|
89
|
+
*/
|
|
90
|
+
reset(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Get current snapshot size for debugging/metrics
|
|
93
|
+
*/
|
|
94
|
+
get size(): number;
|
|
95
|
+
}
|
|
96
|
+
|
|
38
97
|
interface QueryFilter {
|
|
39
98
|
where?: Record<string, any>;
|
|
40
99
|
predicate?: PredicateNode;
|
|
@@ -55,6 +114,9 @@ declare class QueryHandle<T> {
|
|
|
55
114
|
private filter;
|
|
56
115
|
private listeners;
|
|
57
116
|
private currentResults;
|
|
117
|
+
private changeTracker;
|
|
118
|
+
private pendingChanges;
|
|
119
|
+
private changeListeners;
|
|
58
120
|
constructor(syncEngine: SyncEngine, mapName: string, filter?: QueryFilter);
|
|
59
121
|
subscribe(callback: (results: QueryResultItem<T>[]) => void): () => void;
|
|
60
122
|
private loadInitialLocalData;
|
|
@@ -79,6 +141,47 @@ declare class QueryHandle<T> {
|
|
|
79
141
|
* Called by SyncEngine when server sends a live update
|
|
80
142
|
*/
|
|
81
143
|
onUpdate(key: string, value: T | null): void;
|
|
144
|
+
/**
|
|
145
|
+
* Subscribe to change events (Phase 5.1).
|
|
146
|
+
* Returns an unsubscribe function.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* const unsubscribe = handle.onChanges((changes) => {
|
|
151
|
+
* for (const change of changes) {
|
|
152
|
+
* if (change.type === 'add') {
|
|
153
|
+
* console.log('Added:', change.key, change.value);
|
|
154
|
+
* }
|
|
155
|
+
* }
|
|
156
|
+
* });
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
onChanges(listener: (changes: ChangeEvent<T>[]) => void): () => void;
|
|
160
|
+
/**
|
|
161
|
+
* Get and clear pending changes (Phase 5.1).
|
|
162
|
+
* Call this to retrieve all changes since the last consume.
|
|
163
|
+
*/
|
|
164
|
+
consumeChanges(): ChangeEvent<T>[];
|
|
165
|
+
/**
|
|
166
|
+
* Get last change without consuming (Phase 5.1).
|
|
167
|
+
* Returns null if no pending changes.
|
|
168
|
+
*/
|
|
169
|
+
getLastChange(): ChangeEvent<T> | null;
|
|
170
|
+
/**
|
|
171
|
+
* Get all pending changes without consuming (Phase 5.1).
|
|
172
|
+
*/
|
|
173
|
+
getPendingChanges(): ChangeEvent<T>[];
|
|
174
|
+
/**
|
|
175
|
+
* Clear all pending changes (Phase 5.1).
|
|
176
|
+
*/
|
|
177
|
+
clearChanges(): void;
|
|
178
|
+
/**
|
|
179
|
+
* Reset change tracker (Phase 5.1).
|
|
180
|
+
* Use when query filter changes or on reconnect.
|
|
181
|
+
*/
|
|
182
|
+
resetChangeTracker(): void;
|
|
183
|
+
private computeAndNotifyChanges;
|
|
184
|
+
private notifyChangeListeners;
|
|
82
185
|
private notify;
|
|
83
186
|
private getSortedResults;
|
|
84
187
|
getFilter(): QueryFilter;
|
|
@@ -304,6 +407,238 @@ interface OperationDroppedEvent {
|
|
|
304
407
|
key: string;
|
|
305
408
|
}
|
|
306
409
|
|
|
410
|
+
/**
|
|
411
|
+
* Connection Provider Types
|
|
412
|
+
*
|
|
413
|
+
* IConnectionProvider abstracts WebSocket connection handling to support
|
|
414
|
+
* both single-server and cluster modes.
|
|
415
|
+
*/
|
|
416
|
+
/**
|
|
417
|
+
* Events emitted by IConnectionProvider.
|
|
418
|
+
*/
|
|
419
|
+
type ConnectionProviderEvent = 'connected' | 'disconnected' | 'reconnected' | 'message' | 'partitionMapUpdated' | 'error';
|
|
420
|
+
/**
|
|
421
|
+
* Connection event handler type.
|
|
422
|
+
*/
|
|
423
|
+
type ConnectionEventHandler = (...args: any[]) => void;
|
|
424
|
+
/**
|
|
425
|
+
* Abstract interface for WebSocket connection providers.
|
|
426
|
+
*
|
|
427
|
+
* Implementations:
|
|
428
|
+
* - SingleServerProvider: Direct connection to a single server
|
|
429
|
+
* - ClusterClient: Multi-node connection pool with partition routing
|
|
430
|
+
*/
|
|
431
|
+
interface IConnectionProvider {
|
|
432
|
+
/**
|
|
433
|
+
* Connect to the server(s).
|
|
434
|
+
* In cluster mode, connects to all seed nodes.
|
|
435
|
+
*/
|
|
436
|
+
connect(): Promise<void>;
|
|
437
|
+
/**
|
|
438
|
+
* Get connection for a specific key.
|
|
439
|
+
* In cluster mode: routes to partition owner based on key hash.
|
|
440
|
+
* In single-server mode: returns the only connection.
|
|
441
|
+
*
|
|
442
|
+
* @param key - The key to route (used for partition-aware routing)
|
|
443
|
+
* @throws Error if not connected
|
|
444
|
+
*/
|
|
445
|
+
getConnection(key: string): WebSocket;
|
|
446
|
+
/**
|
|
447
|
+
* Get any available connection.
|
|
448
|
+
* Used for subscriptions, metadata requests, and non-key-specific operations.
|
|
449
|
+
*
|
|
450
|
+
* @throws Error if not connected
|
|
451
|
+
*/
|
|
452
|
+
getAnyConnection(): WebSocket;
|
|
453
|
+
/**
|
|
454
|
+
* Check if at least one connection is active and ready.
|
|
455
|
+
*/
|
|
456
|
+
isConnected(): boolean;
|
|
457
|
+
/**
|
|
458
|
+
* Get all connected node IDs.
|
|
459
|
+
* Single-server mode returns ['default'].
|
|
460
|
+
* Cluster mode returns actual node IDs.
|
|
461
|
+
*/
|
|
462
|
+
getConnectedNodes(): string[];
|
|
463
|
+
/**
|
|
464
|
+
* Subscribe to connection events.
|
|
465
|
+
*
|
|
466
|
+
* Events:
|
|
467
|
+
* - 'connected': A connection was established (nodeId?: string)
|
|
468
|
+
* - 'disconnected': A connection was lost (nodeId?: string)
|
|
469
|
+
* - 'reconnected': A connection was re-established after disconnect (nodeId?: string)
|
|
470
|
+
* - 'message': A message was received (nodeId: string, data: any)
|
|
471
|
+
* - 'partitionMapUpdated': Partition map was updated (cluster mode only)
|
|
472
|
+
* - 'error': An error occurred (error: Error)
|
|
473
|
+
*/
|
|
474
|
+
on(event: ConnectionProviderEvent, handler: ConnectionEventHandler): void;
|
|
475
|
+
/**
|
|
476
|
+
* Unsubscribe from connection events.
|
|
477
|
+
*/
|
|
478
|
+
off(event: ConnectionProviderEvent, handler: ConnectionEventHandler): void;
|
|
479
|
+
/**
|
|
480
|
+
* Send a message via the appropriate connection.
|
|
481
|
+
* In cluster mode, routes based on key if provided.
|
|
482
|
+
*
|
|
483
|
+
* @param data - Serialized message data
|
|
484
|
+
* @param key - Optional key for routing (cluster mode)
|
|
485
|
+
*/
|
|
486
|
+
send(data: ArrayBuffer | Uint8Array, key?: string): void;
|
|
487
|
+
/**
|
|
488
|
+
* Close all connections gracefully.
|
|
489
|
+
*/
|
|
490
|
+
close(): Promise<void>;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Configuration for SingleServerProvider.
|
|
494
|
+
*/
|
|
495
|
+
interface SingleServerProviderConfig {
|
|
496
|
+
/** WebSocket URL to connect to */
|
|
497
|
+
url: string;
|
|
498
|
+
/** Maximum reconnection attempts (default: 10) */
|
|
499
|
+
maxReconnectAttempts?: number;
|
|
500
|
+
/** Initial reconnect delay in ms (default: 1000) */
|
|
501
|
+
reconnectDelayMs?: number;
|
|
502
|
+
/** Backoff multiplier for reconnect delay (default: 2) */
|
|
503
|
+
backoffMultiplier?: number;
|
|
504
|
+
/** Maximum reconnect delay in ms (default: 30000) */
|
|
505
|
+
maxReconnectDelayMs?: number;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Registered resolver info returned from server.
|
|
510
|
+
*/
|
|
511
|
+
interface ResolverInfo {
|
|
512
|
+
mapName: string;
|
|
513
|
+
name: string;
|
|
514
|
+
priority?: number;
|
|
515
|
+
keyPattern?: string;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Registration result from server.
|
|
519
|
+
*/
|
|
520
|
+
interface RegisterResult {
|
|
521
|
+
success: boolean;
|
|
522
|
+
error?: string;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Client-side manager for conflict resolvers.
|
|
526
|
+
*
|
|
527
|
+
* Provides API for:
|
|
528
|
+
* - Registering conflict resolvers on server
|
|
529
|
+
* - Unregistering resolvers
|
|
530
|
+
* - Listing registered resolvers
|
|
531
|
+
* - Subscribing to merge rejection events
|
|
532
|
+
*/
|
|
533
|
+
declare class ConflictResolverClient {
|
|
534
|
+
private readonly syncEngine;
|
|
535
|
+
private readonly rejectionListeners;
|
|
536
|
+
private readonly pendingRequests;
|
|
537
|
+
private static readonly REQUEST_TIMEOUT;
|
|
538
|
+
constructor(syncEngine: SyncEngine);
|
|
539
|
+
/**
|
|
540
|
+
* Register a conflict resolver on the server.
|
|
541
|
+
*
|
|
542
|
+
* @param mapName The map to register the resolver for
|
|
543
|
+
* @param resolver The resolver definition
|
|
544
|
+
* @returns Promise resolving to registration result
|
|
545
|
+
*
|
|
546
|
+
* @example
|
|
547
|
+
* ```typescript
|
|
548
|
+
* // Register a first-write-wins resolver for bookings
|
|
549
|
+
* await client.resolvers.register('bookings', {
|
|
550
|
+
* name: 'first-write-wins',
|
|
551
|
+
* code: `
|
|
552
|
+
* if (context.localValue !== undefined) {
|
|
553
|
+
* return { action: 'reject', reason: 'Slot already booked' };
|
|
554
|
+
* }
|
|
555
|
+
* return { action: 'accept', value: context.remoteValue };
|
|
556
|
+
* `,
|
|
557
|
+
* priority: 100,
|
|
558
|
+
* });
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
register<V>(mapName: string, resolver: Omit<ConflictResolverDef<V>, 'fn'>): Promise<RegisterResult>;
|
|
562
|
+
/**
|
|
563
|
+
* Unregister a conflict resolver from the server.
|
|
564
|
+
*
|
|
565
|
+
* @param mapName The map the resolver is registered for
|
|
566
|
+
* @param resolverName The name of the resolver to unregister
|
|
567
|
+
* @returns Promise resolving to unregistration result
|
|
568
|
+
*/
|
|
569
|
+
unregister(mapName: string, resolverName: string): Promise<RegisterResult>;
|
|
570
|
+
/**
|
|
571
|
+
* List registered conflict resolvers on the server.
|
|
572
|
+
*
|
|
573
|
+
* @param mapName Optional - filter by map name
|
|
574
|
+
* @returns Promise resolving to list of resolver info
|
|
575
|
+
*/
|
|
576
|
+
list(mapName?: string): Promise<ResolverInfo[]>;
|
|
577
|
+
/**
|
|
578
|
+
* Subscribe to merge rejection events.
|
|
579
|
+
*
|
|
580
|
+
* @param listener Callback for rejection events
|
|
581
|
+
* @returns Unsubscribe function
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* ```typescript
|
|
585
|
+
* const unsubscribe = client.resolvers.onRejection((rejection) => {
|
|
586
|
+
* console.log(`Merge rejected for ${rejection.key}: ${rejection.reason}`);
|
|
587
|
+
* // Optionally refresh the local value
|
|
588
|
+
* });
|
|
589
|
+
*
|
|
590
|
+
* // Later...
|
|
591
|
+
* unsubscribe();
|
|
592
|
+
* ```
|
|
593
|
+
*/
|
|
594
|
+
onRejection(listener: (rejection: MergeRejection) => void): () => void;
|
|
595
|
+
/**
|
|
596
|
+
* Handle REGISTER_RESOLVER_RESPONSE from server.
|
|
597
|
+
* Called by SyncEngine.
|
|
598
|
+
*/
|
|
599
|
+
handleRegisterResponse(message: {
|
|
600
|
+
requestId: string;
|
|
601
|
+
success: boolean;
|
|
602
|
+
error?: string;
|
|
603
|
+
}): void;
|
|
604
|
+
/**
|
|
605
|
+
* Handle UNREGISTER_RESOLVER_RESPONSE from server.
|
|
606
|
+
* Called by SyncEngine.
|
|
607
|
+
*/
|
|
608
|
+
handleUnregisterResponse(message: {
|
|
609
|
+
requestId: string;
|
|
610
|
+
success: boolean;
|
|
611
|
+
error?: string;
|
|
612
|
+
}): void;
|
|
613
|
+
/**
|
|
614
|
+
* Handle LIST_RESOLVERS_RESPONSE from server.
|
|
615
|
+
* Called by SyncEngine.
|
|
616
|
+
*/
|
|
617
|
+
handleListResponse(message: {
|
|
618
|
+
requestId: string;
|
|
619
|
+
resolvers: ResolverInfo[];
|
|
620
|
+
}): void;
|
|
621
|
+
/**
|
|
622
|
+
* Handle MERGE_REJECTED from server.
|
|
623
|
+
* Called by SyncEngine.
|
|
624
|
+
*/
|
|
625
|
+
handleMergeRejected(message: {
|
|
626
|
+
mapName: string;
|
|
627
|
+
key: string;
|
|
628
|
+
attemptedValue: unknown;
|
|
629
|
+
reason: string;
|
|
630
|
+
timestamp: Timestamp;
|
|
631
|
+
}): void;
|
|
632
|
+
/**
|
|
633
|
+
* Clear all pending requests (e.g., on disconnect).
|
|
634
|
+
*/
|
|
635
|
+
clearPending(): void;
|
|
636
|
+
/**
|
|
637
|
+
* Get the number of registered rejection listeners.
|
|
638
|
+
*/
|
|
639
|
+
get rejectionListenerCount(): number;
|
|
640
|
+
}
|
|
641
|
+
|
|
307
642
|
interface HeartbeatConfig {
|
|
308
643
|
intervalMs: number;
|
|
309
644
|
timeoutMs: number;
|
|
@@ -323,7 +658,10 @@ interface BackoffConfig {
|
|
|
323
658
|
}
|
|
324
659
|
interface SyncEngineConfig {
|
|
325
660
|
nodeId: string;
|
|
326
|
-
|
|
661
|
+
/** @deprecated Use connectionProvider instead */
|
|
662
|
+
serverUrl?: string;
|
|
663
|
+
/** Connection provider (preferred over serverUrl) */
|
|
664
|
+
connectionProvider?: IConnectionProvider;
|
|
327
665
|
storageAdapter: IStorageAdapter;
|
|
328
666
|
reconnectInterval?: number;
|
|
329
667
|
heartbeat?: Partial<HeartbeatConfig>;
|
|
@@ -337,6 +675,8 @@ declare class SyncEngine {
|
|
|
337
675
|
private readonly hlc;
|
|
338
676
|
private readonly stateMachine;
|
|
339
677
|
private readonly backoffConfig;
|
|
678
|
+
private readonly connectionProvider;
|
|
679
|
+
private readonly useConnectionProvider;
|
|
340
680
|
private websocket;
|
|
341
681
|
private opLog;
|
|
342
682
|
private maps;
|
|
@@ -358,6 +698,7 @@ declare class SyncEngine {
|
|
|
358
698
|
private highWaterMarkEmitted;
|
|
359
699
|
private backpressureListeners;
|
|
360
700
|
private pendingWriteConcernPromises;
|
|
701
|
+
private readonly conflictResolverClient;
|
|
361
702
|
constructor(config: SyncEngineConfig);
|
|
362
703
|
/**
|
|
363
704
|
* Get the current connection state
|
|
@@ -384,6 +725,14 @@ declare class SyncEngine {
|
|
|
384
725
|
* Check if fully connected and synced
|
|
385
726
|
*/
|
|
386
727
|
private isConnected;
|
|
728
|
+
/**
|
|
729
|
+
* Initialize connection using IConnectionProvider (Phase 4.5 cluster mode).
|
|
730
|
+
* Sets up event handlers for the connection provider.
|
|
731
|
+
*/
|
|
732
|
+
private initConnectionProvider;
|
|
733
|
+
/**
|
|
734
|
+
* Initialize connection using direct WebSocket (legacy single-server mode).
|
|
735
|
+
*/
|
|
387
736
|
private initConnection;
|
|
388
737
|
private scheduleReconnect;
|
|
389
738
|
private calculateBackoffDelay;
|
|
@@ -391,6 +740,18 @@ declare class SyncEngine {
|
|
|
391
740
|
* Reset backoff counter (called on successful connection)
|
|
392
741
|
*/
|
|
393
742
|
private resetBackoff;
|
|
743
|
+
/**
|
|
744
|
+
* Send a message through the current connection.
|
|
745
|
+
* Uses connectionProvider if in cluster mode, otherwise uses direct websocket.
|
|
746
|
+
* @param message Message object to serialize and send
|
|
747
|
+
* @param key Optional key for routing (cluster mode only)
|
|
748
|
+
* @returns true if message was sent, false otherwise
|
|
749
|
+
*/
|
|
750
|
+
private sendMessage;
|
|
751
|
+
/**
|
|
752
|
+
* Check if we can send messages (connection is ready).
|
|
753
|
+
*/
|
|
754
|
+
private canSend;
|
|
394
755
|
private loadOpLog;
|
|
395
756
|
private saveOpLog;
|
|
396
757
|
registerMap(mapName: string, map: LWWMap<any, any> | ORMap<any, any>): void;
|
|
@@ -439,6 +800,43 @@ declare class SyncEngine {
|
|
|
439
800
|
* Use after fatal errors to start fresh.
|
|
440
801
|
*/
|
|
441
802
|
resetConnection(): void;
|
|
803
|
+
/**
|
|
804
|
+
* Wait for a partition map update from the connection provider.
|
|
805
|
+
* Used when an operation fails with NOT_OWNER error and needs
|
|
806
|
+
* to wait for an updated partition map before retrying.
|
|
807
|
+
*
|
|
808
|
+
* @param timeoutMs - Maximum time to wait (default: 5000ms)
|
|
809
|
+
* @returns Promise that resolves when partition map is updated or times out
|
|
810
|
+
*/
|
|
811
|
+
waitForPartitionMapUpdate(timeoutMs?: number): Promise<void>;
|
|
812
|
+
/**
|
|
813
|
+
* Wait for the connection to be available.
|
|
814
|
+
* Used when an operation fails due to connection issues and needs
|
|
815
|
+
* to wait for reconnection before retrying.
|
|
816
|
+
*
|
|
817
|
+
* @param timeoutMs - Maximum time to wait (default: 10000ms)
|
|
818
|
+
* @returns Promise that resolves when connected or rejects on timeout
|
|
819
|
+
*/
|
|
820
|
+
waitForConnection(timeoutMs?: number): Promise<void>;
|
|
821
|
+
/**
|
|
822
|
+
* Wait for a specific sync state.
|
|
823
|
+
* Useful for waiting until fully connected and synced.
|
|
824
|
+
*
|
|
825
|
+
* @param targetState - The state to wait for
|
|
826
|
+
* @param timeoutMs - Maximum time to wait (default: 30000ms)
|
|
827
|
+
* @returns Promise that resolves when state is reached or rejects on timeout
|
|
828
|
+
*/
|
|
829
|
+
waitForState(targetState: SyncState, timeoutMs?: number): Promise<void>;
|
|
830
|
+
/**
|
|
831
|
+
* Check if the connection provider is connected.
|
|
832
|
+
* Convenience method for failover logic.
|
|
833
|
+
*/
|
|
834
|
+
isProviderConnected(): boolean;
|
|
835
|
+
/**
|
|
836
|
+
* Get the connection provider for direct access.
|
|
837
|
+
* Use with caution - prefer using SyncEngine methods.
|
|
838
|
+
*/
|
|
839
|
+
getConnectionProvider(): IConnectionProvider;
|
|
442
840
|
private resetMap;
|
|
443
841
|
/**
|
|
444
842
|
* Starts the heartbeat mechanism after successful connection.
|
|
@@ -540,6 +938,99 @@ declare class SyncEngine {
|
|
|
540
938
|
* Cancel all pending Write Concern promises (e.g., on disconnect).
|
|
541
939
|
*/
|
|
542
940
|
private cancelAllWriteConcernPromises;
|
|
941
|
+
/** Counter update listeners by name */
|
|
942
|
+
private counterUpdateListeners;
|
|
943
|
+
/**
|
|
944
|
+
* Subscribe to counter updates from server.
|
|
945
|
+
* @param name Counter name
|
|
946
|
+
* @param listener Callback when counter state is updated
|
|
947
|
+
* @returns Unsubscribe function
|
|
948
|
+
*/
|
|
949
|
+
onCounterUpdate(name: string, listener: (state: any) => void): () => void;
|
|
950
|
+
/**
|
|
951
|
+
* Request initial counter state from server.
|
|
952
|
+
* @param name Counter name
|
|
953
|
+
*/
|
|
954
|
+
requestCounter(name: string): void;
|
|
955
|
+
/**
|
|
956
|
+
* Sync local counter state to server.
|
|
957
|
+
* @param name Counter name
|
|
958
|
+
* @param state Counter state to sync
|
|
959
|
+
*/
|
|
960
|
+
syncCounter(name: string, state: any): void;
|
|
961
|
+
/**
|
|
962
|
+
* Handle incoming counter update from server.
|
|
963
|
+
* Called by handleServerMessage for COUNTER_UPDATE messages.
|
|
964
|
+
*/
|
|
965
|
+
private handleCounterUpdate;
|
|
966
|
+
/** Pending entry processor requests by requestId */
|
|
967
|
+
private pendingProcessorRequests;
|
|
968
|
+
/** Pending batch entry processor requests by requestId */
|
|
969
|
+
private pendingBatchProcessorRequests;
|
|
970
|
+
/** Default timeout for entry processor requests (ms) */
|
|
971
|
+
private static readonly PROCESSOR_TIMEOUT;
|
|
972
|
+
/**
|
|
973
|
+
* Execute an entry processor on a single key atomically.
|
|
974
|
+
*
|
|
975
|
+
* @param mapName Name of the map
|
|
976
|
+
* @param key Key to process
|
|
977
|
+
* @param processor Processor definition
|
|
978
|
+
* @returns Promise resolving to the processor result
|
|
979
|
+
*/
|
|
980
|
+
executeOnKey<V, R = V>(mapName: string, key: string, processor: EntryProcessorDef<V, R>): Promise<EntryProcessorResult<R>>;
|
|
981
|
+
/**
|
|
982
|
+
* Execute an entry processor on multiple keys.
|
|
983
|
+
*
|
|
984
|
+
* @param mapName Name of the map
|
|
985
|
+
* @param keys Keys to process
|
|
986
|
+
* @param processor Processor definition
|
|
987
|
+
* @returns Promise resolving to a map of key -> result
|
|
988
|
+
*/
|
|
989
|
+
executeOnKeys<V, R = V>(mapName: string, keys: string[], processor: EntryProcessorDef<V, R>): Promise<Map<string, EntryProcessorResult<R>>>;
|
|
990
|
+
/**
|
|
991
|
+
* Handle entry processor response from server.
|
|
992
|
+
* Called by handleServerMessage for ENTRY_PROCESS_RESPONSE messages.
|
|
993
|
+
*/
|
|
994
|
+
private handleEntryProcessResponse;
|
|
995
|
+
/**
|
|
996
|
+
* Handle entry processor batch response from server.
|
|
997
|
+
* Called by handleServerMessage for ENTRY_PROCESS_BATCH_RESPONSE messages.
|
|
998
|
+
*/
|
|
999
|
+
private handleEntryProcessBatchResponse;
|
|
1000
|
+
/** Message listeners for journal and other generic messages */
|
|
1001
|
+
private messageListeners;
|
|
1002
|
+
/**
|
|
1003
|
+
* Subscribe to all incoming messages.
|
|
1004
|
+
* Used by EventJournalReader to receive journal events.
|
|
1005
|
+
*
|
|
1006
|
+
* @param event Event type (currently only 'message')
|
|
1007
|
+
* @param handler Message handler
|
|
1008
|
+
*/
|
|
1009
|
+
on(event: 'message', handler: (message: any) => void): void;
|
|
1010
|
+
/**
|
|
1011
|
+
* Unsubscribe from incoming messages.
|
|
1012
|
+
*
|
|
1013
|
+
* @param event Event type (currently only 'message')
|
|
1014
|
+
* @param handler Message handler to remove
|
|
1015
|
+
*/
|
|
1016
|
+
off(event: 'message', handler: (message: any) => void): void;
|
|
1017
|
+
/**
|
|
1018
|
+
* Send a message to the server.
|
|
1019
|
+
* Public method for EventJournalReader and other components.
|
|
1020
|
+
*
|
|
1021
|
+
* @param message Message object to send
|
|
1022
|
+
*/
|
|
1023
|
+
send(message: any): void;
|
|
1024
|
+
/**
|
|
1025
|
+
* Emit message to all listeners.
|
|
1026
|
+
* Called internally when a message is received.
|
|
1027
|
+
*/
|
|
1028
|
+
private emitMessage;
|
|
1029
|
+
/**
|
|
1030
|
+
* Get the conflict resolver client for registering custom resolvers
|
|
1031
|
+
* and subscribing to merge rejection events.
|
|
1032
|
+
*/
|
|
1033
|
+
getConflictResolverClient(): ConflictResolverClient;
|
|
543
1034
|
}
|
|
544
1035
|
|
|
545
1036
|
interface ILock {
|
|
@@ -558,95 +1049,353 @@ declare class DistributedLock implements ILock {
|
|
|
558
1049
|
isLocked(): boolean;
|
|
559
1050
|
}
|
|
560
1051
|
|
|
561
|
-
|
|
562
|
-
|
|
1052
|
+
/**
|
|
1053
|
+
* Client-side handle for a PN Counter.
|
|
1054
|
+
*
|
|
1055
|
+
* Wraps the core PNCounterImpl and integrates with SyncEngine for:
|
|
1056
|
+
* - Automatic sync to server when counter changes
|
|
1057
|
+
* - Receiving remote updates from other clients
|
|
1058
|
+
* - Local persistence via IStorageAdapter (IndexedDB in browser)
|
|
1059
|
+
*
|
|
1060
|
+
* @example
|
|
1061
|
+
* ```typescript
|
|
1062
|
+
* const counter = client.getPNCounter('likes:post-123');
|
|
1063
|
+
* counter.increment(); // Immediate local update + sync to server
|
|
1064
|
+
*
|
|
1065
|
+
* counter.subscribe((value) => {
|
|
1066
|
+
* console.log('Current likes:', value);
|
|
1067
|
+
* });
|
|
1068
|
+
* ```
|
|
1069
|
+
*/
|
|
1070
|
+
declare class PNCounterHandle implements PNCounter {
|
|
1071
|
+
private readonly counter;
|
|
1072
|
+
private readonly name;
|
|
563
1073
|
private readonly syncEngine;
|
|
564
|
-
private readonly
|
|
565
|
-
private
|
|
566
|
-
private
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
serverUrl: string;
|
|
570
|
-
storage: IStorageAdapter;
|
|
571
|
-
backoff?: Partial<BackoffConfig>;
|
|
572
|
-
backpressure?: Partial<BackpressureConfig>;
|
|
573
|
-
});
|
|
574
|
-
start(): Promise<void>;
|
|
575
|
-
setAuthToken(token: string): void;
|
|
576
|
-
setAuthTokenProvider(provider: () => Promise<string | null>): void;
|
|
1074
|
+
private readonly storageAdapter?;
|
|
1075
|
+
private syncScheduled;
|
|
1076
|
+
private persistScheduled;
|
|
1077
|
+
private unsubscribeFromUpdates?;
|
|
1078
|
+
constructor(name: string, nodeId: string, syncEngine: SyncEngine, storageAdapter?: IStorageAdapter);
|
|
577
1079
|
/**
|
|
578
|
-
*
|
|
1080
|
+
* Restore counter state from local storage.
|
|
1081
|
+
* Called during construction to recover offline state.
|
|
579
1082
|
*/
|
|
580
|
-
|
|
1083
|
+
private restoreFromStorage;
|
|
581
1084
|
/**
|
|
582
|
-
*
|
|
583
|
-
*
|
|
1085
|
+
* Persist counter state to local storage.
|
|
1086
|
+
* Debounced to avoid excessive writes during rapid operations.
|
|
584
1087
|
*/
|
|
585
|
-
|
|
1088
|
+
private schedulePersist;
|
|
586
1089
|
/**
|
|
587
|
-
*
|
|
588
|
-
* @param name The name of the topic.
|
|
1090
|
+
* Actually persist state to storage.
|
|
589
1091
|
*/
|
|
590
|
-
|
|
1092
|
+
private persistToStorage;
|
|
591
1093
|
/**
|
|
592
|
-
*
|
|
593
|
-
* @param name The name of the map.
|
|
594
|
-
* @returns An LWWMap instance.
|
|
1094
|
+
* Get current counter value.
|
|
595
1095
|
*/
|
|
596
|
-
|
|
1096
|
+
get(): number;
|
|
597
1097
|
/**
|
|
598
|
-
*
|
|
599
|
-
* @param name The name of the map.
|
|
600
|
-
* @returns An ORMap instance.
|
|
1098
|
+
* Increment by 1 and return new value.
|
|
601
1099
|
*/
|
|
602
|
-
|
|
603
|
-
private restoreORMap;
|
|
604
|
-
private persistORMapKey;
|
|
605
|
-
private persistORMapTombstones;
|
|
1100
|
+
increment(): number;
|
|
606
1101
|
/**
|
|
607
|
-
*
|
|
1102
|
+
* Decrement by 1 and return new value.
|
|
608
1103
|
*/
|
|
609
|
-
|
|
1104
|
+
decrement(): number;
|
|
610
1105
|
/**
|
|
611
|
-
*
|
|
1106
|
+
* Add delta (positive or negative) and return new value.
|
|
612
1107
|
*/
|
|
613
|
-
|
|
1108
|
+
addAndGet(delta: number): number;
|
|
614
1109
|
/**
|
|
615
|
-
*
|
|
616
|
-
* @param listener Callback function called on each state change
|
|
617
|
-
* @returns Unsubscribe function
|
|
1110
|
+
* Get state for sync.
|
|
618
1111
|
*/
|
|
619
|
-
|
|
1112
|
+
getState(): PNCounterState;
|
|
620
1113
|
/**
|
|
621
|
-
*
|
|
622
|
-
* @param limit Maximum number of entries to return
|
|
1114
|
+
* Merge remote state.
|
|
623
1115
|
*/
|
|
624
|
-
|
|
1116
|
+
merge(remote: PNCounterState): void;
|
|
625
1117
|
/**
|
|
626
|
-
*
|
|
627
|
-
* Use after fatal errors to start fresh.
|
|
1118
|
+
* Subscribe to value changes.
|
|
628
1119
|
*/
|
|
629
|
-
|
|
1120
|
+
subscribe(listener: (value: number) => void): () => void;
|
|
630
1121
|
/**
|
|
631
|
-
* Get the
|
|
1122
|
+
* Get the counter name.
|
|
632
1123
|
*/
|
|
633
|
-
|
|
1124
|
+
getName(): string;
|
|
634
1125
|
/**
|
|
635
|
-
*
|
|
1126
|
+
* Cleanup resources.
|
|
636
1127
|
*/
|
|
637
|
-
|
|
1128
|
+
dispose(): void;
|
|
638
1129
|
/**
|
|
639
|
-
*
|
|
1130
|
+
* Schedule sync to server with debouncing.
|
|
1131
|
+
* Batches rapid increments to avoid network spam.
|
|
640
1132
|
*/
|
|
641
|
-
|
|
1133
|
+
private scheduleSync;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* Serialized journal event from network (bigint as string).
|
|
1138
|
+
*/
|
|
1139
|
+
interface JournalEventData {
|
|
1140
|
+
sequence: string;
|
|
1141
|
+
type: JournalEventType;
|
|
1142
|
+
mapName: string;
|
|
1143
|
+
key: string;
|
|
1144
|
+
value?: unknown;
|
|
1145
|
+
previousValue?: unknown;
|
|
1146
|
+
timestamp: {
|
|
1147
|
+
millis: number;
|
|
1148
|
+
counter: number;
|
|
1149
|
+
nodeId: string;
|
|
1150
|
+
};
|
|
1151
|
+
nodeId: string;
|
|
1152
|
+
metadata?: Record<string, unknown>;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Options for journal subscription.
|
|
1156
|
+
*/
|
|
1157
|
+
interface JournalSubscribeOptions {
|
|
1158
|
+
/** Start from specific sequence */
|
|
1159
|
+
fromSequence?: bigint;
|
|
1160
|
+
/** Filter by map name */
|
|
1161
|
+
mapName?: string;
|
|
1162
|
+
/** Filter by event types */
|
|
1163
|
+
types?: JournalEventType[];
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Client-side Event Journal Reader.
|
|
1167
|
+
* Communicates with server to read and subscribe to journal events.
|
|
1168
|
+
*/
|
|
1169
|
+
declare class EventJournalReader {
|
|
1170
|
+
private readonly syncEngine;
|
|
1171
|
+
private readonly listeners;
|
|
1172
|
+
private subscriptionCounter;
|
|
1173
|
+
constructor(syncEngine: SyncEngine);
|
|
642
1174
|
/**
|
|
643
|
-
*
|
|
1175
|
+
* Read events from sequence with optional limit.
|
|
644
1176
|
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
|
|
649
|
-
|
|
1177
|
+
* @param sequence Starting sequence (inclusive)
|
|
1178
|
+
* @param limit Maximum events to return (default: 100)
|
|
1179
|
+
* @returns Promise resolving to array of events
|
|
1180
|
+
*/
|
|
1181
|
+
readFrom(sequence: bigint, limit?: number): Promise<JournalEvent[]>;
|
|
1182
|
+
/**
|
|
1183
|
+
* Read events for a specific map.
|
|
1184
|
+
*
|
|
1185
|
+
* @param mapName Map name to filter
|
|
1186
|
+
* @param sequence Starting sequence (default: 0n)
|
|
1187
|
+
* @param limit Maximum events to return (default: 100)
|
|
1188
|
+
*/
|
|
1189
|
+
readMapEvents(mapName: string, sequence?: bigint, limit?: number): Promise<JournalEvent[]>;
|
|
1190
|
+
/**
|
|
1191
|
+
* Subscribe to new journal events.
|
|
1192
|
+
*
|
|
1193
|
+
* @param listener Callback for each event
|
|
1194
|
+
* @param options Subscription options
|
|
1195
|
+
* @returns Unsubscribe function
|
|
1196
|
+
*/
|
|
1197
|
+
subscribe(listener: (event: JournalEvent) => void, options?: JournalSubscribeOptions): () => void;
|
|
1198
|
+
/**
|
|
1199
|
+
* Get the latest sequence number from server.
|
|
1200
|
+
*/
|
|
1201
|
+
getLatestSequence(): Promise<bigint>;
|
|
1202
|
+
/**
|
|
1203
|
+
* Parse network event data to JournalEvent.
|
|
1204
|
+
*/
|
|
1205
|
+
private parseEvent;
|
|
1206
|
+
/**
|
|
1207
|
+
* Generate unique request ID.
|
|
1208
|
+
*/
|
|
1209
|
+
private generateRequestId;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Cluster mode configuration for TopGunClient.
|
|
1214
|
+
* When provided, the client connects to multiple nodes with partition-aware routing.
|
|
1215
|
+
*/
|
|
1216
|
+
interface TopGunClusterConfig {
|
|
1217
|
+
/** Initial seed nodes (at least one required) */
|
|
1218
|
+
seeds: string[];
|
|
1219
|
+
/** Connection pool size per node (default: 1) */
|
|
1220
|
+
connectionsPerNode?: number;
|
|
1221
|
+
/** Enable smart routing to partition owner (default: true) */
|
|
1222
|
+
smartRouting?: boolean;
|
|
1223
|
+
/** Partition map refresh interval in ms (default: 30000) */
|
|
1224
|
+
partitionMapRefreshMs?: number;
|
|
1225
|
+
/** Connection timeout per node in ms (default: 5000) */
|
|
1226
|
+
connectionTimeoutMs?: number;
|
|
1227
|
+
/** Retry attempts for failed operations (default: 3) */
|
|
1228
|
+
retryAttempts?: number;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Default values for cluster configuration
|
|
1232
|
+
*/
|
|
1233
|
+
declare const DEFAULT_CLUSTER_CONFIG: Required<Omit<TopGunClusterConfig, 'seeds'>>;
|
|
1234
|
+
/**
|
|
1235
|
+
* TopGunClient configuration options
|
|
1236
|
+
*/
|
|
1237
|
+
interface TopGunClientConfig {
|
|
1238
|
+
/** Unique node identifier (auto-generated if not provided) */
|
|
1239
|
+
nodeId?: string;
|
|
1240
|
+
/** Single-server mode: WebSocket URL to connect to */
|
|
1241
|
+
serverUrl?: string;
|
|
1242
|
+
/** Cluster mode: Configuration for multi-node routing */
|
|
1243
|
+
cluster?: TopGunClusterConfig;
|
|
1244
|
+
/** Storage adapter for local persistence */
|
|
1245
|
+
storage: IStorageAdapter;
|
|
1246
|
+
/** Backoff configuration for reconnection */
|
|
1247
|
+
backoff?: Partial<BackoffConfig>;
|
|
1248
|
+
/** Backpressure configuration */
|
|
1249
|
+
backpressure?: Partial<BackpressureConfig>;
|
|
1250
|
+
}
|
|
1251
|
+
declare class TopGunClient {
|
|
1252
|
+
private readonly nodeId;
|
|
1253
|
+
private readonly syncEngine;
|
|
1254
|
+
private readonly maps;
|
|
1255
|
+
private readonly storageAdapter;
|
|
1256
|
+
private readonly topicHandles;
|
|
1257
|
+
private readonly counters;
|
|
1258
|
+
private readonly clusterClient?;
|
|
1259
|
+
private readonly isClusterMode;
|
|
1260
|
+
private readonly clusterConfig?;
|
|
1261
|
+
constructor(config: TopGunClientConfig);
|
|
1262
|
+
start(): Promise<void>;
|
|
1263
|
+
setAuthToken(token: string): void;
|
|
1264
|
+
setAuthTokenProvider(provider: () => Promise<string | null>): void;
|
|
1265
|
+
/**
|
|
1266
|
+
* Creates a live query subscription for a map.
|
|
1267
|
+
*/
|
|
1268
|
+
query<T>(mapName: string, filter: QueryFilter): QueryHandle<T>;
|
|
1269
|
+
/**
|
|
1270
|
+
* Retrieves a distributed lock instance.
|
|
1271
|
+
* @param name The name of the lock.
|
|
1272
|
+
*/
|
|
1273
|
+
getLock(name: string): DistributedLock;
|
|
1274
|
+
/**
|
|
1275
|
+
* Retrieves a topic handle for Pub/Sub messaging.
|
|
1276
|
+
* @param name The name of the topic.
|
|
1277
|
+
*/
|
|
1278
|
+
topic(name: string): TopicHandle;
|
|
1279
|
+
/**
|
|
1280
|
+
* Retrieves a PN Counter instance. If the counter doesn't exist locally, it's created.
|
|
1281
|
+
* PN Counters support increment and decrement operations that work offline
|
|
1282
|
+
* and sync to server when connected.
|
|
1283
|
+
*
|
|
1284
|
+
* @param name The name of the counter (e.g., 'likes:post-123')
|
|
1285
|
+
* @returns A PNCounterHandle instance
|
|
1286
|
+
*
|
|
1287
|
+
* @example
|
|
1288
|
+
* ```typescript
|
|
1289
|
+
* const likes = client.getPNCounter('likes:post-123');
|
|
1290
|
+
* likes.increment(); // +1
|
|
1291
|
+
* likes.decrement(); // -1
|
|
1292
|
+
* likes.addAndGet(10); // +10
|
|
1293
|
+
*
|
|
1294
|
+
* likes.subscribe((value) => {
|
|
1295
|
+
* console.log('Current likes:', value);
|
|
1296
|
+
* });
|
|
1297
|
+
* ```
|
|
1298
|
+
*/
|
|
1299
|
+
getPNCounter(name: string): PNCounterHandle;
|
|
1300
|
+
/**
|
|
1301
|
+
* Retrieves an LWWMap instance. If the map doesn't exist locally, it's created.
|
|
1302
|
+
* @param name The name of the map.
|
|
1303
|
+
* @returns An LWWMap instance.
|
|
1304
|
+
*/
|
|
1305
|
+
getMap<K, V>(name: string): LWWMap<K, V>;
|
|
1306
|
+
/**
|
|
1307
|
+
* Retrieves an ORMap instance. If the map doesn't exist locally, it's created.
|
|
1308
|
+
* @param name The name of the map.
|
|
1309
|
+
* @returns An ORMap instance.
|
|
1310
|
+
*/
|
|
1311
|
+
getORMap<K, V>(name: string): ORMap<K, V>;
|
|
1312
|
+
private restoreORMap;
|
|
1313
|
+
private persistORMapKey;
|
|
1314
|
+
private persistORMapTombstones;
|
|
1315
|
+
/**
|
|
1316
|
+
* Closes the client, disconnecting from the server and cleaning up resources.
|
|
1317
|
+
*/
|
|
1318
|
+
close(): void;
|
|
1319
|
+
/**
|
|
1320
|
+
* Check if running in cluster mode
|
|
1321
|
+
*/
|
|
1322
|
+
isCluster(): boolean;
|
|
1323
|
+
/**
|
|
1324
|
+
* Get list of connected cluster nodes (cluster mode only)
|
|
1325
|
+
* @returns Array of connected node IDs, or empty array in single-server mode
|
|
1326
|
+
*/
|
|
1327
|
+
getConnectedNodes(): string[];
|
|
1328
|
+
/**
|
|
1329
|
+
* Get the current partition map version (cluster mode only)
|
|
1330
|
+
* @returns Partition map version, or 0 in single-server mode
|
|
1331
|
+
*/
|
|
1332
|
+
getPartitionMapVersion(): number;
|
|
1333
|
+
/**
|
|
1334
|
+
* Check if direct routing is active (cluster mode only)
|
|
1335
|
+
* Direct routing sends operations directly to partition owners.
|
|
1336
|
+
* @returns true if routing is active, false otherwise
|
|
1337
|
+
*/
|
|
1338
|
+
isRoutingActive(): boolean;
|
|
1339
|
+
/**
|
|
1340
|
+
* Get health status for all cluster nodes (cluster mode only)
|
|
1341
|
+
* @returns Map of node IDs to their health status
|
|
1342
|
+
*/
|
|
1343
|
+
getClusterHealth(): Map<string, NodeHealth>;
|
|
1344
|
+
/**
|
|
1345
|
+
* Force refresh of partition map (cluster mode only)
|
|
1346
|
+
* Use this after detecting routing errors.
|
|
1347
|
+
*/
|
|
1348
|
+
refreshPartitionMap(): Promise<void>;
|
|
1349
|
+
/**
|
|
1350
|
+
* Get cluster router statistics (cluster mode only)
|
|
1351
|
+
*/
|
|
1352
|
+
getClusterStats(): {
|
|
1353
|
+
mapVersion: number;
|
|
1354
|
+
partitionCount: number;
|
|
1355
|
+
nodeCount: number;
|
|
1356
|
+
lastRefresh: number;
|
|
1357
|
+
isStale: boolean;
|
|
1358
|
+
} | null;
|
|
1359
|
+
/**
|
|
1360
|
+
* Get the current connection state
|
|
1361
|
+
*/
|
|
1362
|
+
getConnectionState(): SyncState;
|
|
1363
|
+
/**
|
|
1364
|
+
* Subscribe to connection state changes
|
|
1365
|
+
* @param listener Callback function called on each state change
|
|
1366
|
+
* @returns Unsubscribe function
|
|
1367
|
+
*/
|
|
1368
|
+
onConnectionStateChange(listener: (event: StateChangeEvent) => void): () => void;
|
|
1369
|
+
/**
|
|
1370
|
+
* Get state machine history for debugging
|
|
1371
|
+
* @param limit Maximum number of entries to return
|
|
1372
|
+
*/
|
|
1373
|
+
getStateHistory(limit?: number): StateChangeEvent[];
|
|
1374
|
+
/**
|
|
1375
|
+
* Reset the connection and state machine.
|
|
1376
|
+
* Use after fatal errors to start fresh.
|
|
1377
|
+
*/
|
|
1378
|
+
resetConnection(): void;
|
|
1379
|
+
/**
|
|
1380
|
+
* Get the current number of pending (unacknowledged) operations.
|
|
1381
|
+
*/
|
|
1382
|
+
getPendingOpsCount(): number;
|
|
1383
|
+
/**
|
|
1384
|
+
* Get the current backpressure status.
|
|
1385
|
+
*/
|
|
1386
|
+
getBackpressureStatus(): BackpressureStatus;
|
|
1387
|
+
/**
|
|
1388
|
+
* Returns true if writes are currently paused due to backpressure.
|
|
1389
|
+
*/
|
|
1390
|
+
isBackpressurePaused(): boolean;
|
|
1391
|
+
/**
|
|
1392
|
+
* Subscribe to backpressure events.
|
|
1393
|
+
*
|
|
1394
|
+
* Available events:
|
|
1395
|
+
* - 'backpressure:high': Emitted when pending ops reach high water mark
|
|
1396
|
+
* - 'backpressure:low': Emitted when pending ops drop below low water mark
|
|
1397
|
+
* - 'backpressure:paused': Emitted when writes are paused (pause strategy)
|
|
1398
|
+
* - 'backpressure:resumed': Emitted when writes resume after being paused
|
|
650
1399
|
* - 'operation:dropped': Emitted when an operation is dropped (drop-oldest strategy)
|
|
651
1400
|
*
|
|
652
1401
|
* @param event Event name
|
|
@@ -669,6 +1418,144 @@ declare class TopGunClient {
|
|
|
669
1418
|
* ```
|
|
670
1419
|
*/
|
|
671
1420
|
onBackpressure(event: 'backpressure:high' | 'backpressure:low' | 'backpressure:paused' | 'backpressure:resumed' | 'operation:dropped', listener: (data?: BackpressureThresholdEvent | OperationDroppedEvent) => void): () => void;
|
|
1421
|
+
/**
|
|
1422
|
+
* Execute an entry processor on a single key atomically.
|
|
1423
|
+
*
|
|
1424
|
+
* Entry processors solve the read-modify-write race condition by executing
|
|
1425
|
+
* user-defined logic atomically on the server where the data lives.
|
|
1426
|
+
*
|
|
1427
|
+
* @param mapName Name of the map
|
|
1428
|
+
* @param key Key to process
|
|
1429
|
+
* @param processor Processor definition with name, code, and optional args
|
|
1430
|
+
* @returns Promise resolving to the processor result
|
|
1431
|
+
*
|
|
1432
|
+
* @example
|
|
1433
|
+
* ```typescript
|
|
1434
|
+
* // Increment a counter atomically
|
|
1435
|
+
* const result = await client.executeOnKey('stats', 'pageViews', {
|
|
1436
|
+
* name: 'increment',
|
|
1437
|
+
* code: `
|
|
1438
|
+
* const current = value ?? 0;
|
|
1439
|
+
* return { value: current + 1, result: current + 1 };
|
|
1440
|
+
* `,
|
|
1441
|
+
* });
|
|
1442
|
+
*
|
|
1443
|
+
* // Using built-in processor
|
|
1444
|
+
* import { BuiltInProcessors } from '@topgunbuild/core';
|
|
1445
|
+
* const result = await client.executeOnKey(
|
|
1446
|
+
* 'stats',
|
|
1447
|
+
* 'pageViews',
|
|
1448
|
+
* BuiltInProcessors.INCREMENT(1)
|
|
1449
|
+
* );
|
|
1450
|
+
* ```
|
|
1451
|
+
*/
|
|
1452
|
+
executeOnKey<V, R = V>(mapName: string, key: string, processor: EntryProcessorDef<V, R>): Promise<EntryProcessorResult<R>>;
|
|
1453
|
+
/**
|
|
1454
|
+
* Execute an entry processor on multiple keys.
|
|
1455
|
+
*
|
|
1456
|
+
* Each key is processed atomically. The operation returns when all keys
|
|
1457
|
+
* have been processed.
|
|
1458
|
+
*
|
|
1459
|
+
* @param mapName Name of the map
|
|
1460
|
+
* @param keys Keys to process
|
|
1461
|
+
* @param processor Processor definition
|
|
1462
|
+
* @returns Promise resolving to a map of key -> result
|
|
1463
|
+
*
|
|
1464
|
+
* @example
|
|
1465
|
+
* ```typescript
|
|
1466
|
+
* // Reset multiple counters
|
|
1467
|
+
* const results = await client.executeOnKeys(
|
|
1468
|
+
* 'stats',
|
|
1469
|
+
* ['pageViews', 'uniqueVisitors', 'bounceRate'],
|
|
1470
|
+
* {
|
|
1471
|
+
* name: 'reset',
|
|
1472
|
+
* code: `return { value: 0, result: value };`, // Returns old value
|
|
1473
|
+
* }
|
|
1474
|
+
* );
|
|
1475
|
+
*
|
|
1476
|
+
* for (const [key, result] of results) {
|
|
1477
|
+
* console.log(`${key}: was ${result.result}, now 0`);
|
|
1478
|
+
* }
|
|
1479
|
+
* ```
|
|
1480
|
+
*/
|
|
1481
|
+
executeOnKeys<V, R = V>(mapName: string, keys: string[], processor: EntryProcessorDef<V, R>): Promise<Map<string, EntryProcessorResult<R>>>;
|
|
1482
|
+
/** Cached EventJournalReader instance */
|
|
1483
|
+
private journalReader?;
|
|
1484
|
+
/**
|
|
1485
|
+
* Get the Event Journal reader for subscribing to and reading
|
|
1486
|
+
* map change events.
|
|
1487
|
+
*
|
|
1488
|
+
* The Event Journal provides:
|
|
1489
|
+
* - Append-only log of all map changes (PUT, UPDATE, DELETE)
|
|
1490
|
+
* - Subscription to real-time events
|
|
1491
|
+
* - Historical event replay
|
|
1492
|
+
* - Audit trail for compliance
|
|
1493
|
+
*
|
|
1494
|
+
* @returns EventJournalReader instance
|
|
1495
|
+
*
|
|
1496
|
+
* @example
|
|
1497
|
+
* ```typescript
|
|
1498
|
+
* const journal = client.getEventJournal();
|
|
1499
|
+
*
|
|
1500
|
+
* // Subscribe to all events
|
|
1501
|
+
* const unsubscribe = journal.subscribe((event) => {
|
|
1502
|
+
* console.log(`${event.type} on ${event.mapName}:${event.key}`);
|
|
1503
|
+
* });
|
|
1504
|
+
*
|
|
1505
|
+
* // Subscribe to specific map
|
|
1506
|
+
* journal.subscribe(
|
|
1507
|
+
* (event) => console.log('User changed:', event.key),
|
|
1508
|
+
* { mapName: 'users' }
|
|
1509
|
+
* );
|
|
1510
|
+
*
|
|
1511
|
+
* // Read historical events
|
|
1512
|
+
* const events = await journal.readFrom(0n, 100);
|
|
1513
|
+
* ```
|
|
1514
|
+
*/
|
|
1515
|
+
getEventJournal(): EventJournalReader;
|
|
1516
|
+
/**
|
|
1517
|
+
* Get the conflict resolver client for registering custom merge resolvers.
|
|
1518
|
+
*
|
|
1519
|
+
* Conflict resolvers allow you to customize how merge conflicts are handled
|
|
1520
|
+
* on the server. You can implement business logic like:
|
|
1521
|
+
* - First-write-wins for booking systems
|
|
1522
|
+
* - Numeric constraints (non-negative, min/max)
|
|
1523
|
+
* - Owner-only modifications
|
|
1524
|
+
* - Custom merge strategies
|
|
1525
|
+
*
|
|
1526
|
+
* @returns ConflictResolverClient instance
|
|
1527
|
+
*
|
|
1528
|
+
* @example
|
|
1529
|
+
* ```typescript
|
|
1530
|
+
* const resolvers = client.getConflictResolvers();
|
|
1531
|
+
*
|
|
1532
|
+
* // Register a first-write-wins resolver
|
|
1533
|
+
* await resolvers.register('bookings', {
|
|
1534
|
+
* name: 'first-write-wins',
|
|
1535
|
+
* code: `
|
|
1536
|
+
* if (context.localValue !== undefined) {
|
|
1537
|
+
* return { action: 'reject', reason: 'Slot already booked' };
|
|
1538
|
+
* }
|
|
1539
|
+
* return { action: 'accept', value: context.remoteValue };
|
|
1540
|
+
* `,
|
|
1541
|
+
* priority: 100,
|
|
1542
|
+
* });
|
|
1543
|
+
*
|
|
1544
|
+
* // Subscribe to merge rejections
|
|
1545
|
+
* resolvers.onRejection((rejection) => {
|
|
1546
|
+
* console.log(`Merge rejected: ${rejection.reason}`);
|
|
1547
|
+
* // Optionally refresh local state
|
|
1548
|
+
* });
|
|
1549
|
+
*
|
|
1550
|
+
* // List registered resolvers
|
|
1551
|
+
* const registered = await resolvers.list('bookings');
|
|
1552
|
+
* console.log('Active resolvers:', registered);
|
|
1553
|
+
*
|
|
1554
|
+
* // Unregister when done
|
|
1555
|
+
* await resolvers.unregister('bookings', 'first-write-wins');
|
|
1556
|
+
* ```
|
|
1557
|
+
*/
|
|
1558
|
+
getConflictResolvers(): ConflictResolverClient;
|
|
672
1559
|
}
|
|
673
1560
|
|
|
674
1561
|
interface TopGunConfig {
|
|
@@ -797,6 +1684,574 @@ declare class BackpressureError extends Error {
|
|
|
797
1684
|
constructor(pendingCount: number, maxPending: number);
|
|
798
1685
|
}
|
|
799
1686
|
|
|
1687
|
+
/**
|
|
1688
|
+
* ConnectionPool - Manages WebSocket connections to multiple cluster nodes
|
|
1689
|
+
*
|
|
1690
|
+
* Phase 4: Partition-Aware Client Routing
|
|
1691
|
+
*
|
|
1692
|
+
* Features:
|
|
1693
|
+
* - Maintains connections to all known cluster nodes
|
|
1694
|
+
* - Automatic reconnection with exponential backoff
|
|
1695
|
+
* - Health monitoring and status tracking
|
|
1696
|
+
* - Connection lifecycle management
|
|
1697
|
+
*/
|
|
1698
|
+
|
|
1699
|
+
interface ConnectionPoolEvents {
|
|
1700
|
+
'node:connected': (nodeId: string) => void;
|
|
1701
|
+
'node:disconnected': (nodeId: string, reason: string) => void;
|
|
1702
|
+
'node:healthy': (nodeId: string) => void;
|
|
1703
|
+
'node:unhealthy': (nodeId: string, reason: string) => void;
|
|
1704
|
+
'message': (nodeId: string, message: any) => void;
|
|
1705
|
+
'error': (nodeId: string, error: Error) => void;
|
|
1706
|
+
}
|
|
1707
|
+
declare class ConnectionPool {
|
|
1708
|
+
private readonly listeners;
|
|
1709
|
+
private readonly config;
|
|
1710
|
+
private readonly connections;
|
|
1711
|
+
private primaryNodeId;
|
|
1712
|
+
private healthCheckTimer;
|
|
1713
|
+
private authToken;
|
|
1714
|
+
constructor(config?: Partial<ConnectionPoolConfig>);
|
|
1715
|
+
on(event: string, listener: (...args: any[]) => void): this;
|
|
1716
|
+
off(event: string, listener: (...args: any[]) => void): this;
|
|
1717
|
+
emit(event: string, ...args: any[]): boolean;
|
|
1718
|
+
removeAllListeners(event?: string): this;
|
|
1719
|
+
/**
|
|
1720
|
+
* Set authentication token for all connections
|
|
1721
|
+
*/
|
|
1722
|
+
setAuthToken(token: string): void;
|
|
1723
|
+
/**
|
|
1724
|
+
* Add a node to the connection pool
|
|
1725
|
+
*/
|
|
1726
|
+
addNode(nodeId: string, endpoint: string): Promise<void>;
|
|
1727
|
+
/**
|
|
1728
|
+
* Remove a node from the connection pool
|
|
1729
|
+
*/
|
|
1730
|
+
removeNode(nodeId: string): Promise<void>;
|
|
1731
|
+
/**
|
|
1732
|
+
* Get connection for a specific node
|
|
1733
|
+
*/
|
|
1734
|
+
getConnection(nodeId: string): WebSocket | null;
|
|
1735
|
+
/**
|
|
1736
|
+
* Get primary connection (first/seed node)
|
|
1737
|
+
*/
|
|
1738
|
+
getPrimaryConnection(): WebSocket | null;
|
|
1739
|
+
/**
|
|
1740
|
+
* Get any healthy connection
|
|
1741
|
+
*/
|
|
1742
|
+
getAnyHealthyConnection(): {
|
|
1743
|
+
nodeId: string;
|
|
1744
|
+
socket: WebSocket;
|
|
1745
|
+
} | null;
|
|
1746
|
+
/**
|
|
1747
|
+
* Send message to a specific node
|
|
1748
|
+
*/
|
|
1749
|
+
send(nodeId: string, message: any): boolean;
|
|
1750
|
+
/**
|
|
1751
|
+
* Send message to primary node
|
|
1752
|
+
*/
|
|
1753
|
+
sendToPrimary(message: any): boolean;
|
|
1754
|
+
/**
|
|
1755
|
+
* Get health status for all nodes
|
|
1756
|
+
*/
|
|
1757
|
+
getHealthStatus(): Map<string, NodeHealth>;
|
|
1758
|
+
/**
|
|
1759
|
+
* Get list of connected node IDs
|
|
1760
|
+
*/
|
|
1761
|
+
getConnectedNodes(): string[];
|
|
1762
|
+
/**
|
|
1763
|
+
* Get all node IDs
|
|
1764
|
+
*/
|
|
1765
|
+
getAllNodes(): string[];
|
|
1766
|
+
/**
|
|
1767
|
+
* Check if node is connected and authenticated
|
|
1768
|
+
*/
|
|
1769
|
+
isNodeConnected(nodeId: string): boolean;
|
|
1770
|
+
/**
|
|
1771
|
+
* Check if connected to a specific node.
|
|
1772
|
+
* Alias for isNodeConnected() for IConnectionProvider compatibility.
|
|
1773
|
+
*/
|
|
1774
|
+
isConnected(nodeId: string): boolean;
|
|
1775
|
+
/**
|
|
1776
|
+
* Start health monitoring
|
|
1777
|
+
*/
|
|
1778
|
+
startHealthCheck(): void;
|
|
1779
|
+
/**
|
|
1780
|
+
* Stop health monitoring
|
|
1781
|
+
*/
|
|
1782
|
+
stopHealthCheck(): void;
|
|
1783
|
+
/**
|
|
1784
|
+
* Close all connections and cleanup
|
|
1785
|
+
*/
|
|
1786
|
+
close(): void;
|
|
1787
|
+
private connect;
|
|
1788
|
+
private sendAuth;
|
|
1789
|
+
private handleMessage;
|
|
1790
|
+
private flushPendingMessages;
|
|
1791
|
+
private scheduleReconnect;
|
|
1792
|
+
private performHealthCheck;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
/**
|
|
1796
|
+
* PartitionRouter - Routes operations to the correct cluster node
|
|
1797
|
+
*
|
|
1798
|
+
* Phase 4: Partition-Aware Client Routing
|
|
1799
|
+
*
|
|
1800
|
+
* Features:
|
|
1801
|
+
* - Maintains local copy of partition map
|
|
1802
|
+
* - Routes keys to owner nodes using consistent hashing
|
|
1803
|
+
* - Handles stale routing with automatic refresh
|
|
1804
|
+
* - Supports fallback to server-side forwarding
|
|
1805
|
+
*/
|
|
1806
|
+
|
|
1807
|
+
interface RoutingResult {
|
|
1808
|
+
nodeId: string;
|
|
1809
|
+
partitionId: number;
|
|
1810
|
+
isOwner: boolean;
|
|
1811
|
+
isBackup: boolean;
|
|
1812
|
+
}
|
|
1813
|
+
interface PartitionRouterEvents {
|
|
1814
|
+
'partitionMap:updated': (version: number, changesCount: number) => void;
|
|
1815
|
+
'partitionMap:stale': (currentVersion: number, lastRefresh: number) => void;
|
|
1816
|
+
'routing:miss': (key: string, expectedOwner: string, actualOwner: string) => void;
|
|
1817
|
+
}
|
|
1818
|
+
declare class PartitionRouter {
|
|
1819
|
+
private readonly listeners;
|
|
1820
|
+
private readonly config;
|
|
1821
|
+
private readonly connectionPool;
|
|
1822
|
+
private partitionMap;
|
|
1823
|
+
private lastRefreshTime;
|
|
1824
|
+
private refreshTimer;
|
|
1825
|
+
private pendingRefresh;
|
|
1826
|
+
constructor(connectionPool: ConnectionPool, config?: Partial<PartitionRouterConfig>);
|
|
1827
|
+
on(event: string, listener: (...args: any[]) => void): this;
|
|
1828
|
+
off(event: string, listener: (...args: any[]) => void): this;
|
|
1829
|
+
once(event: string, listener: (...args: any[]) => void): this;
|
|
1830
|
+
emit(event: string, ...args: any[]): boolean;
|
|
1831
|
+
removeListener(event: string, listener: (...args: any[]) => void): this;
|
|
1832
|
+
removeAllListeners(event?: string): this;
|
|
1833
|
+
/**
|
|
1834
|
+
* Get the partition ID for a given key
|
|
1835
|
+
*/
|
|
1836
|
+
getPartitionId(key: string): number;
|
|
1837
|
+
/**
|
|
1838
|
+
* Route a key to the owner node
|
|
1839
|
+
*/
|
|
1840
|
+
route(key: string): RoutingResult | null;
|
|
1841
|
+
/**
|
|
1842
|
+
* Route a key and get the WebSocket connection to use
|
|
1843
|
+
*/
|
|
1844
|
+
routeToConnection(key: string): {
|
|
1845
|
+
nodeId: string;
|
|
1846
|
+
socket: WebSocket;
|
|
1847
|
+
} | null;
|
|
1848
|
+
/**
|
|
1849
|
+
* Get routing info for multiple keys (batch routing)
|
|
1850
|
+
*/
|
|
1851
|
+
routeBatch(keys: string[]): Map<string, RoutingResult[]>;
|
|
1852
|
+
/**
|
|
1853
|
+
* Get all partitions owned by a specific node
|
|
1854
|
+
*/
|
|
1855
|
+
getPartitionsForNode(nodeId: string): number[];
|
|
1856
|
+
/**
|
|
1857
|
+
* Get current partition map version
|
|
1858
|
+
*/
|
|
1859
|
+
getMapVersion(): number;
|
|
1860
|
+
/**
|
|
1861
|
+
* Check if partition map is available
|
|
1862
|
+
*/
|
|
1863
|
+
hasPartitionMap(): boolean;
|
|
1864
|
+
/**
|
|
1865
|
+
* Get owner node for a key.
|
|
1866
|
+
* Returns null if partition map is not available.
|
|
1867
|
+
*/
|
|
1868
|
+
getOwner(key: string): string | null;
|
|
1869
|
+
/**
|
|
1870
|
+
* Get backup nodes for a key.
|
|
1871
|
+
* Returns empty array if partition map is not available.
|
|
1872
|
+
*/
|
|
1873
|
+
getBackups(key: string): string[];
|
|
1874
|
+
/**
|
|
1875
|
+
* Get the full partition map.
|
|
1876
|
+
* Returns null if not available.
|
|
1877
|
+
*/
|
|
1878
|
+
getMap(): PartitionMap | null;
|
|
1879
|
+
/**
|
|
1880
|
+
* Update entire partition map.
|
|
1881
|
+
* Only accepts newer versions.
|
|
1882
|
+
*/
|
|
1883
|
+
updateMap(map: PartitionMap): boolean;
|
|
1884
|
+
/**
|
|
1885
|
+
* Update a single partition (for delta updates).
|
|
1886
|
+
*/
|
|
1887
|
+
updatePartition(partitionId: number, owner: string, backups: string[]): void;
|
|
1888
|
+
/**
|
|
1889
|
+
* Check if partition map is stale
|
|
1890
|
+
*/
|
|
1891
|
+
isMapStale(): boolean;
|
|
1892
|
+
/**
|
|
1893
|
+
* Request fresh partition map from server
|
|
1894
|
+
*/
|
|
1895
|
+
refreshPartitionMap(): Promise<void>;
|
|
1896
|
+
/**
|
|
1897
|
+
* Start periodic partition map refresh
|
|
1898
|
+
*/
|
|
1899
|
+
startPeriodicRefresh(): void;
|
|
1900
|
+
/**
|
|
1901
|
+
* Stop periodic refresh
|
|
1902
|
+
*/
|
|
1903
|
+
stopPeriodicRefresh(): void;
|
|
1904
|
+
/**
|
|
1905
|
+
* Handle NOT_OWNER error from server
|
|
1906
|
+
*/
|
|
1907
|
+
handleNotOwnerError(key: string, actualOwner: string, newMapVersion: number): void;
|
|
1908
|
+
/**
|
|
1909
|
+
* Get statistics about routing
|
|
1910
|
+
*/
|
|
1911
|
+
getStats(): {
|
|
1912
|
+
mapVersion: number;
|
|
1913
|
+
partitionCount: number;
|
|
1914
|
+
nodeCount: number;
|
|
1915
|
+
lastRefresh: number;
|
|
1916
|
+
isStale: boolean;
|
|
1917
|
+
};
|
|
1918
|
+
/**
|
|
1919
|
+
* Cleanup resources
|
|
1920
|
+
*/
|
|
1921
|
+
close(): void;
|
|
1922
|
+
private handlePartitionMap;
|
|
1923
|
+
private handlePartitionMapDelta;
|
|
1924
|
+
private applyPartitionChange;
|
|
1925
|
+
private updateConnectionPool;
|
|
1926
|
+
private doRefreshPartitionMap;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
/**
|
|
1930
|
+
* ClusterClient - Cluster-aware client wrapper
|
|
1931
|
+
*
|
|
1932
|
+
* Phase 4: Partition-Aware Client Routing
|
|
1933
|
+
* Phase 4.5: Implements IConnectionProvider for SyncEngine abstraction
|
|
1934
|
+
*
|
|
1935
|
+
* Wraps the standard TopGunClient with cluster-aware routing capabilities.
|
|
1936
|
+
* Coordinates between ConnectionPool and PartitionRouter for optimal
|
|
1937
|
+
* request routing in a clustered environment.
|
|
1938
|
+
*/
|
|
1939
|
+
|
|
1940
|
+
interface ClusterClientEvents {
|
|
1941
|
+
'connected': () => void;
|
|
1942
|
+
'disconnected': (reason: string) => void;
|
|
1943
|
+
'partitionMap:ready': (version: number) => void;
|
|
1944
|
+
'routing:active': () => void;
|
|
1945
|
+
'error': (error: Error) => void;
|
|
1946
|
+
'circuit:open': (nodeId: string) => void;
|
|
1947
|
+
'circuit:closed': (nodeId: string) => void;
|
|
1948
|
+
'circuit:half-open': (nodeId: string) => void;
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Circuit breaker state for a node.
|
|
1952
|
+
*/
|
|
1953
|
+
interface CircuitState {
|
|
1954
|
+
/** Number of consecutive failures */
|
|
1955
|
+
failures: number;
|
|
1956
|
+
/** Timestamp of last failure */
|
|
1957
|
+
lastFailure: number;
|
|
1958
|
+
/** Current circuit state */
|
|
1959
|
+
state: 'closed' | 'open' | 'half-open';
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Routing metrics for monitoring smart routing effectiveness.
|
|
1963
|
+
*/
|
|
1964
|
+
interface RoutingMetrics {
|
|
1965
|
+
/** Operations routed directly to partition owner */
|
|
1966
|
+
directRoutes: number;
|
|
1967
|
+
/** Operations falling back to any node (owner unavailable) */
|
|
1968
|
+
fallbackRoutes: number;
|
|
1969
|
+
/** Operations when partition map is missing/stale */
|
|
1970
|
+
partitionMisses: number;
|
|
1971
|
+
/** Total routing decisions made */
|
|
1972
|
+
totalRoutes: number;
|
|
1973
|
+
}
|
|
1974
|
+
type ClusterRoutingMode = 'direct' | 'forward';
|
|
1975
|
+
/**
|
|
1976
|
+
* ClusterClient implements IConnectionProvider for multi-node cluster mode.
|
|
1977
|
+
* It provides partition-aware routing and connection management.
|
|
1978
|
+
*/
|
|
1979
|
+
declare class ClusterClient implements IConnectionProvider {
|
|
1980
|
+
private readonly listeners;
|
|
1981
|
+
private readonly connectionPool;
|
|
1982
|
+
private readonly partitionRouter;
|
|
1983
|
+
private readonly config;
|
|
1984
|
+
private initialized;
|
|
1985
|
+
private routingActive;
|
|
1986
|
+
private readonly routingMetrics;
|
|
1987
|
+
private readonly circuits;
|
|
1988
|
+
private readonly circuitBreakerConfig;
|
|
1989
|
+
constructor(config: ClusterClientConfig);
|
|
1990
|
+
on(event: string, listener: (...args: any[]) => void): this;
|
|
1991
|
+
off(event: string, listener: (...args: any[]) => void): this;
|
|
1992
|
+
emit(event: string, ...args: any[]): boolean;
|
|
1993
|
+
removeAllListeners(event?: string): this;
|
|
1994
|
+
/**
|
|
1995
|
+
* Connect to cluster nodes (IConnectionProvider interface).
|
|
1996
|
+
* Alias for start() method.
|
|
1997
|
+
*/
|
|
1998
|
+
connect(): Promise<void>;
|
|
1999
|
+
/**
|
|
2000
|
+
* Get connection for a specific key (IConnectionProvider interface).
|
|
2001
|
+
* Routes to partition owner based on key hash when smart routing is enabled.
|
|
2002
|
+
* @throws Error if not connected
|
|
2003
|
+
*/
|
|
2004
|
+
getConnection(key: string): WebSocket;
|
|
2005
|
+
/**
|
|
2006
|
+
* Get fallback connection when owner is unavailable.
|
|
2007
|
+
* @throws Error if no connection available
|
|
2008
|
+
*/
|
|
2009
|
+
private getFallbackConnection;
|
|
2010
|
+
/**
|
|
2011
|
+
* Request a partition map refresh in the background.
|
|
2012
|
+
* Called when routing to an unknown/disconnected owner.
|
|
2013
|
+
*/
|
|
2014
|
+
private requestPartitionMapRefresh;
|
|
2015
|
+
/**
|
|
2016
|
+
* Request partition map from a specific node.
|
|
2017
|
+
* Called on first node connection.
|
|
2018
|
+
*/
|
|
2019
|
+
private requestPartitionMapFromNode;
|
|
2020
|
+
/**
|
|
2021
|
+
* Check if at least one connection is active (IConnectionProvider interface).
|
|
2022
|
+
*/
|
|
2023
|
+
isConnected(): boolean;
|
|
2024
|
+
/**
|
|
2025
|
+
* Send data via the appropriate connection (IConnectionProvider interface).
|
|
2026
|
+
* Routes based on key if provided.
|
|
2027
|
+
*/
|
|
2028
|
+
send(data: ArrayBuffer | Uint8Array, key?: string): void;
|
|
2029
|
+
/**
|
|
2030
|
+
* Send data with automatic retry and rerouting on failure.
|
|
2031
|
+
* @param data - Data to send
|
|
2032
|
+
* @param key - Optional key for routing
|
|
2033
|
+
* @param options - Retry options
|
|
2034
|
+
* @throws Error after max retries exceeded
|
|
2035
|
+
*/
|
|
2036
|
+
sendWithRetry(data: ArrayBuffer | Uint8Array, key?: string, options?: {
|
|
2037
|
+
maxRetries?: number;
|
|
2038
|
+
retryDelayMs?: number;
|
|
2039
|
+
retryOnNotOwner?: boolean;
|
|
2040
|
+
}): Promise<void>;
|
|
2041
|
+
/**
|
|
2042
|
+
* Check if an error is retryable.
|
|
2043
|
+
*/
|
|
2044
|
+
private isRetryableError;
|
|
2045
|
+
/**
|
|
2046
|
+
* Wait for partition map update.
|
|
2047
|
+
*/
|
|
2048
|
+
private waitForPartitionMapUpdateInternal;
|
|
2049
|
+
/**
|
|
2050
|
+
* Wait for at least one connection to be available.
|
|
2051
|
+
*/
|
|
2052
|
+
private waitForConnectionInternal;
|
|
2053
|
+
/**
|
|
2054
|
+
* Helper delay function.
|
|
2055
|
+
*/
|
|
2056
|
+
private delay;
|
|
2057
|
+
/**
|
|
2058
|
+
* Initialize cluster connections
|
|
2059
|
+
*/
|
|
2060
|
+
start(): Promise<void>;
|
|
2061
|
+
/**
|
|
2062
|
+
* Set authentication token
|
|
2063
|
+
*/
|
|
2064
|
+
setAuthToken(token: string): void;
|
|
2065
|
+
/**
|
|
2066
|
+
* Send operation with automatic routing (legacy API for cluster operations).
|
|
2067
|
+
* @deprecated Use send(data, key) for IConnectionProvider interface
|
|
2068
|
+
*/
|
|
2069
|
+
sendMessage(key: string, message: any): boolean;
|
|
2070
|
+
/**
|
|
2071
|
+
* Send directly to partition owner
|
|
2072
|
+
*/
|
|
2073
|
+
sendDirect(key: string, message: any): boolean;
|
|
2074
|
+
/**
|
|
2075
|
+
* Send to primary node for server-side forwarding
|
|
2076
|
+
*/
|
|
2077
|
+
sendForward(message: any): boolean;
|
|
2078
|
+
/**
|
|
2079
|
+
* Send batch of operations with routing
|
|
2080
|
+
*/
|
|
2081
|
+
sendBatch(operations: Array<{
|
|
2082
|
+
key: string;
|
|
2083
|
+
message: any;
|
|
2084
|
+
}>): Map<string, boolean>;
|
|
2085
|
+
/**
|
|
2086
|
+
* Get connection pool health status
|
|
2087
|
+
*/
|
|
2088
|
+
getHealthStatus(): Map<string, NodeHealth>;
|
|
2089
|
+
/**
|
|
2090
|
+
* Get partition router stats
|
|
2091
|
+
*/
|
|
2092
|
+
getRouterStats(): ReturnType<PartitionRouter['getStats']>;
|
|
2093
|
+
/**
|
|
2094
|
+
* Get routing metrics for monitoring smart routing effectiveness.
|
|
2095
|
+
*/
|
|
2096
|
+
getRoutingMetrics(): RoutingMetrics;
|
|
2097
|
+
/**
|
|
2098
|
+
* Reset routing metrics counters.
|
|
2099
|
+
* Useful for monitoring intervals.
|
|
2100
|
+
*/
|
|
2101
|
+
resetRoutingMetrics(): void;
|
|
2102
|
+
/**
|
|
2103
|
+
* Check if cluster routing is active
|
|
2104
|
+
*/
|
|
2105
|
+
isRoutingActive(): boolean;
|
|
2106
|
+
/**
|
|
2107
|
+
* Get list of connected nodes
|
|
2108
|
+
*/
|
|
2109
|
+
getConnectedNodes(): string[];
|
|
2110
|
+
/**
|
|
2111
|
+
* Check if cluster client is initialized
|
|
2112
|
+
*/
|
|
2113
|
+
isInitialized(): boolean;
|
|
2114
|
+
/**
|
|
2115
|
+
* Force refresh of partition map
|
|
2116
|
+
*/
|
|
2117
|
+
refreshPartitionMap(): Promise<void>;
|
|
2118
|
+
/**
|
|
2119
|
+
* Shutdown cluster client (IConnectionProvider interface).
|
|
2120
|
+
*/
|
|
2121
|
+
close(): Promise<void>;
|
|
2122
|
+
/**
|
|
2123
|
+
* Get the connection pool (for internal use)
|
|
2124
|
+
*/
|
|
2125
|
+
getConnectionPool(): ConnectionPool;
|
|
2126
|
+
/**
|
|
2127
|
+
* Get the partition router (for internal use)
|
|
2128
|
+
*/
|
|
2129
|
+
getPartitionRouter(): PartitionRouter;
|
|
2130
|
+
/**
|
|
2131
|
+
* Get any healthy WebSocket connection (IConnectionProvider interface).
|
|
2132
|
+
* @throws Error if not connected
|
|
2133
|
+
*/
|
|
2134
|
+
getAnyConnection(): WebSocket;
|
|
2135
|
+
/**
|
|
2136
|
+
* Get any healthy WebSocket connection, or null if none available.
|
|
2137
|
+
* Use this for optional connection checks.
|
|
2138
|
+
*/
|
|
2139
|
+
getAnyConnectionOrNull(): WebSocket | null;
|
|
2140
|
+
/**
|
|
2141
|
+
* Get circuit breaker state for a node.
|
|
2142
|
+
*/
|
|
2143
|
+
getCircuit(nodeId: string): CircuitState;
|
|
2144
|
+
/**
|
|
2145
|
+
* Check if a node can be used (circuit not open).
|
|
2146
|
+
*/
|
|
2147
|
+
canUseNode(nodeId: string): boolean;
|
|
2148
|
+
/**
|
|
2149
|
+
* Record a successful operation to a node.
|
|
2150
|
+
* Resets circuit breaker on success.
|
|
2151
|
+
*/
|
|
2152
|
+
recordSuccess(nodeId: string): void;
|
|
2153
|
+
/**
|
|
2154
|
+
* Record a failed operation to a node.
|
|
2155
|
+
* Opens circuit breaker after threshold failures.
|
|
2156
|
+
*/
|
|
2157
|
+
recordFailure(nodeId: string): void;
|
|
2158
|
+
/**
|
|
2159
|
+
* Get all circuit breaker states.
|
|
2160
|
+
*/
|
|
2161
|
+
getCircuitStates(): Map<string, CircuitState>;
|
|
2162
|
+
/**
|
|
2163
|
+
* Reset circuit breaker for a specific node.
|
|
2164
|
+
*/
|
|
2165
|
+
resetCircuit(nodeId: string): void;
|
|
2166
|
+
/**
|
|
2167
|
+
* Reset all circuit breakers.
|
|
2168
|
+
*/
|
|
2169
|
+
resetAllCircuits(): void;
|
|
2170
|
+
private setupEventHandlers;
|
|
2171
|
+
private waitForPartitionMap;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
/**
|
|
2175
|
+
* SingleServerProvider implements IConnectionProvider for single-server mode.
|
|
2176
|
+
*
|
|
2177
|
+
* This is an adapter that wraps direct WebSocket connection handling,
|
|
2178
|
+
* providing the same interface used by ClusterClient for multi-node mode.
|
|
2179
|
+
*/
|
|
2180
|
+
declare class SingleServerProvider implements IConnectionProvider {
|
|
2181
|
+
private readonly url;
|
|
2182
|
+
private readonly config;
|
|
2183
|
+
private ws;
|
|
2184
|
+
private reconnectAttempts;
|
|
2185
|
+
private reconnectTimer;
|
|
2186
|
+
private isClosing;
|
|
2187
|
+
private listeners;
|
|
2188
|
+
constructor(config: SingleServerProviderConfig);
|
|
2189
|
+
/**
|
|
2190
|
+
* Connect to the WebSocket server.
|
|
2191
|
+
*/
|
|
2192
|
+
connect(): Promise<void>;
|
|
2193
|
+
/**
|
|
2194
|
+
* Get connection for a specific key.
|
|
2195
|
+
* In single-server mode, key is ignored.
|
|
2196
|
+
*/
|
|
2197
|
+
getConnection(_key: string): WebSocket;
|
|
2198
|
+
/**
|
|
2199
|
+
* Get any available connection.
|
|
2200
|
+
*/
|
|
2201
|
+
getAnyConnection(): WebSocket;
|
|
2202
|
+
/**
|
|
2203
|
+
* Check if connected.
|
|
2204
|
+
*/
|
|
2205
|
+
isConnected(): boolean;
|
|
2206
|
+
/**
|
|
2207
|
+
* Get connected node IDs.
|
|
2208
|
+
* Single-server mode returns ['default'] when connected.
|
|
2209
|
+
*/
|
|
2210
|
+
getConnectedNodes(): string[];
|
|
2211
|
+
/**
|
|
2212
|
+
* Subscribe to connection events.
|
|
2213
|
+
*/
|
|
2214
|
+
on(event: ConnectionProviderEvent, handler: ConnectionEventHandler): void;
|
|
2215
|
+
/**
|
|
2216
|
+
* Unsubscribe from connection events.
|
|
2217
|
+
*/
|
|
2218
|
+
off(event: ConnectionProviderEvent, handler: ConnectionEventHandler): void;
|
|
2219
|
+
/**
|
|
2220
|
+
* Send data via the WebSocket connection.
|
|
2221
|
+
* In single-server mode, key parameter is ignored.
|
|
2222
|
+
*/
|
|
2223
|
+
send(data: ArrayBuffer | Uint8Array, _key?: string): void;
|
|
2224
|
+
/**
|
|
2225
|
+
* Close the WebSocket connection.
|
|
2226
|
+
*/
|
|
2227
|
+
close(): Promise<void>;
|
|
2228
|
+
/**
|
|
2229
|
+
* Emit an event to all listeners.
|
|
2230
|
+
*/
|
|
2231
|
+
private emit;
|
|
2232
|
+
/**
|
|
2233
|
+
* Schedule a reconnection attempt with exponential backoff.
|
|
2234
|
+
*/
|
|
2235
|
+
private scheduleReconnect;
|
|
2236
|
+
/**
|
|
2237
|
+
* Calculate backoff delay with exponential increase.
|
|
2238
|
+
*/
|
|
2239
|
+
private calculateBackoffDelay;
|
|
2240
|
+
/**
|
|
2241
|
+
* Get the WebSocket URL this provider connects to.
|
|
2242
|
+
*/
|
|
2243
|
+
getUrl(): string;
|
|
2244
|
+
/**
|
|
2245
|
+
* Get current reconnection attempt count.
|
|
2246
|
+
*/
|
|
2247
|
+
getReconnectAttempts(): number;
|
|
2248
|
+
/**
|
|
2249
|
+
* Reset reconnection counter.
|
|
2250
|
+
* Called externally after successful authentication.
|
|
2251
|
+
*/
|
|
2252
|
+
resetReconnectAttempts(): void;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
800
2255
|
declare const logger: pino.Logger<never, boolean>;
|
|
801
2256
|
|
|
802
|
-
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 };
|
|
2257
|
+
export { type BackoffConfig, type BackpressureConfig, BackpressureError, type BackpressureStatus, type BackpressureStrategy, type BackpressureThresholdEvent, type ChangeEvent, ChangeTracker, type CircuitState, ClusterClient, type ClusterClientEvents, type ClusterRoutingMode, ConflictResolverClient, type ConnectionEventHandler, ConnectionPool, type ConnectionPoolEvents, type ConnectionProviderEvent, DEFAULT_BACKPRESSURE_CONFIG, DEFAULT_CLUSTER_CONFIG, EncryptedStorageAdapter, EventJournalReader, type HeartbeatConfig, type IConnectionProvider, IDBAdapter, type IStorageAdapter, type JournalEventData, type JournalSubscribeOptions, type OpLogEntry, type OperationDroppedEvent, PNCounterHandle, PartitionRouter, type PartitionRouterEvents, type QueryFilter, QueryHandle, type QueryResultItem, type QueryResultSource, type RegisterResult, type ResolverInfo, 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 };
|