@topgunbuild/client 0.3.0 → 0.5.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 CHANGED
@@ -1,4 +1,4 @@
1
- import { LWWRecord, ORMapRecord, PredicateNode, LWWMap, ORMap, Timestamp, HLC, NodeHealth, ConnectionPoolConfig, PartitionRouterConfig, PartitionMap, ClusterClientConfig } from '@topgunbuild/core';
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 };