@topgunbuild/client 0.3.0 → 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 +653 -2
- package/dist/index.d.ts +653 -2
- package/dist/index.js +1258 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1228 -7
- 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;
|
|
@@ -402,6 +505,140 @@ interface SingleServerProviderConfig {
|
|
|
402
505
|
maxReconnectDelayMs?: number;
|
|
403
506
|
}
|
|
404
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
|
+
|
|
405
642
|
interface HeartbeatConfig {
|
|
406
643
|
intervalMs: number;
|
|
407
644
|
timeoutMs: number;
|
|
@@ -461,6 +698,7 @@ declare class SyncEngine {
|
|
|
461
698
|
private highWaterMarkEmitted;
|
|
462
699
|
private backpressureListeners;
|
|
463
700
|
private pendingWriteConcernPromises;
|
|
701
|
+
private readonly conflictResolverClient;
|
|
464
702
|
constructor(config: SyncEngineConfig);
|
|
465
703
|
/**
|
|
466
704
|
* Get the current connection state
|
|
@@ -700,6 +938,99 @@ declare class SyncEngine {
|
|
|
700
938
|
* Cancel all pending Write Concern promises (e.g., on disconnect).
|
|
701
939
|
*/
|
|
702
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;
|
|
703
1034
|
}
|
|
704
1035
|
|
|
705
1036
|
interface ILock {
|
|
@@ -718,6 +1049,166 @@ declare class DistributedLock implements ILock {
|
|
|
718
1049
|
isLocked(): boolean;
|
|
719
1050
|
}
|
|
720
1051
|
|
|
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;
|
|
1073
|
+
private readonly syncEngine;
|
|
1074
|
+
private readonly storageAdapter?;
|
|
1075
|
+
private syncScheduled;
|
|
1076
|
+
private persistScheduled;
|
|
1077
|
+
private unsubscribeFromUpdates?;
|
|
1078
|
+
constructor(name: string, nodeId: string, syncEngine: SyncEngine, storageAdapter?: IStorageAdapter);
|
|
1079
|
+
/**
|
|
1080
|
+
* Restore counter state from local storage.
|
|
1081
|
+
* Called during construction to recover offline state.
|
|
1082
|
+
*/
|
|
1083
|
+
private restoreFromStorage;
|
|
1084
|
+
/**
|
|
1085
|
+
* Persist counter state to local storage.
|
|
1086
|
+
* Debounced to avoid excessive writes during rapid operations.
|
|
1087
|
+
*/
|
|
1088
|
+
private schedulePersist;
|
|
1089
|
+
/**
|
|
1090
|
+
* Actually persist state to storage.
|
|
1091
|
+
*/
|
|
1092
|
+
private persistToStorage;
|
|
1093
|
+
/**
|
|
1094
|
+
* Get current counter value.
|
|
1095
|
+
*/
|
|
1096
|
+
get(): number;
|
|
1097
|
+
/**
|
|
1098
|
+
* Increment by 1 and return new value.
|
|
1099
|
+
*/
|
|
1100
|
+
increment(): number;
|
|
1101
|
+
/**
|
|
1102
|
+
* Decrement by 1 and return new value.
|
|
1103
|
+
*/
|
|
1104
|
+
decrement(): number;
|
|
1105
|
+
/**
|
|
1106
|
+
* Add delta (positive or negative) and return new value.
|
|
1107
|
+
*/
|
|
1108
|
+
addAndGet(delta: number): number;
|
|
1109
|
+
/**
|
|
1110
|
+
* Get state for sync.
|
|
1111
|
+
*/
|
|
1112
|
+
getState(): PNCounterState;
|
|
1113
|
+
/**
|
|
1114
|
+
* Merge remote state.
|
|
1115
|
+
*/
|
|
1116
|
+
merge(remote: PNCounterState): void;
|
|
1117
|
+
/**
|
|
1118
|
+
* Subscribe to value changes.
|
|
1119
|
+
*/
|
|
1120
|
+
subscribe(listener: (value: number) => void): () => void;
|
|
1121
|
+
/**
|
|
1122
|
+
* Get the counter name.
|
|
1123
|
+
*/
|
|
1124
|
+
getName(): string;
|
|
1125
|
+
/**
|
|
1126
|
+
* Cleanup resources.
|
|
1127
|
+
*/
|
|
1128
|
+
dispose(): void;
|
|
1129
|
+
/**
|
|
1130
|
+
* Schedule sync to server with debouncing.
|
|
1131
|
+
* Batches rapid increments to avoid network spam.
|
|
1132
|
+
*/
|
|
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);
|
|
1174
|
+
/**
|
|
1175
|
+
* Read events from sequence with optional limit.
|
|
1176
|
+
*
|
|
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
|
+
|
|
721
1212
|
/**
|
|
722
1213
|
* Cluster mode configuration for TopGunClient.
|
|
723
1214
|
* When provided, the client connects to multiple nodes with partition-aware routing.
|
|
@@ -763,6 +1254,7 @@ declare class TopGunClient {
|
|
|
763
1254
|
private readonly maps;
|
|
764
1255
|
private readonly storageAdapter;
|
|
765
1256
|
private readonly topicHandles;
|
|
1257
|
+
private readonly counters;
|
|
766
1258
|
private readonly clusterClient?;
|
|
767
1259
|
private readonly isClusterMode;
|
|
768
1260
|
private readonly clusterConfig?;
|
|
@@ -784,6 +1276,27 @@ declare class TopGunClient {
|
|
|
784
1276
|
* @param name The name of the topic.
|
|
785
1277
|
*/
|
|
786
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;
|
|
787
1300
|
/**
|
|
788
1301
|
* Retrieves an LWWMap instance. If the map doesn't exist locally, it's created.
|
|
789
1302
|
* @param name The name of the map.
|
|
@@ -905,6 +1418,144 @@ declare class TopGunClient {
|
|
|
905
1418
|
* ```
|
|
906
1419
|
*/
|
|
907
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;
|
|
908
1559
|
}
|
|
909
1560
|
|
|
910
1561
|
interface TopGunConfig {
|
|
@@ -1603,4 +2254,4 @@ declare class SingleServerProvider implements IConnectionProvider {
|
|
|
1603
2254
|
|
|
1604
2255
|
declare const logger: pino.Logger<never, boolean>;
|
|
1605
2256
|
|
|
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 };
|
|
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 };
|