agentdb 1.5.9 → 1.6.1

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.
Files changed (68) hide show
  1. package/README.md +11 -11
  2. package/dist/agentdb.min.js +4 -4
  3. package/dist/cli/agentdb-cli.d.ts +29 -0
  4. package/dist/cli/agentdb-cli.d.ts.map +1 -1
  5. package/dist/cli/agentdb-cli.js +1009 -34
  6. package/dist/cli/agentdb-cli.js.map +1 -1
  7. package/dist/controllers/ContextSynthesizer.d.ts +65 -0
  8. package/dist/controllers/ContextSynthesizer.d.ts.map +1 -0
  9. package/dist/controllers/ContextSynthesizer.js +208 -0
  10. package/dist/controllers/ContextSynthesizer.js.map +1 -0
  11. package/dist/controllers/HNSWIndex.d.ts +128 -0
  12. package/dist/controllers/HNSWIndex.d.ts.map +1 -0
  13. package/dist/controllers/HNSWIndex.js +361 -0
  14. package/dist/controllers/HNSWIndex.js.map +1 -0
  15. package/dist/controllers/MMRDiversityRanker.d.ts +50 -0
  16. package/dist/controllers/MMRDiversityRanker.d.ts.map +1 -0
  17. package/dist/controllers/MMRDiversityRanker.js +130 -0
  18. package/dist/controllers/MMRDiversityRanker.js.map +1 -0
  19. package/dist/controllers/MetadataFilter.d.ts +70 -0
  20. package/dist/controllers/MetadataFilter.d.ts.map +1 -0
  21. package/dist/controllers/MetadataFilter.js +243 -0
  22. package/dist/controllers/MetadataFilter.js.map +1 -0
  23. package/dist/controllers/QUICClient.d.ts +109 -0
  24. package/dist/controllers/QUICClient.d.ts.map +1 -0
  25. package/dist/controllers/QUICClient.js +299 -0
  26. package/dist/controllers/QUICClient.js.map +1 -0
  27. package/dist/controllers/QUICServer.d.ts +121 -0
  28. package/dist/controllers/QUICServer.d.ts.map +1 -0
  29. package/dist/controllers/QUICServer.js +383 -0
  30. package/dist/controllers/QUICServer.js.map +1 -0
  31. package/dist/controllers/SyncCoordinator.d.ts +120 -0
  32. package/dist/controllers/SyncCoordinator.d.ts.map +1 -0
  33. package/dist/controllers/SyncCoordinator.js +441 -0
  34. package/dist/controllers/SyncCoordinator.js.map +1 -0
  35. package/dist/controllers/WASMVectorSearch.d.ts.map +1 -1
  36. package/dist/controllers/WASMVectorSearch.js +10 -2
  37. package/dist/controllers/WASMVectorSearch.js.map +1 -1
  38. package/dist/controllers/index.d.ts +14 -0
  39. package/dist/controllers/index.d.ts.map +1 -1
  40. package/dist/controllers/index.js +7 -0
  41. package/dist/controllers/index.js.map +1 -1
  42. package/dist/examples/quic-sync-example.d.ts +9 -0
  43. package/dist/examples/quic-sync-example.d.ts.map +1 -0
  44. package/dist/examples/quic-sync-example.js +169 -0
  45. package/dist/examples/quic-sync-example.js.map +1 -0
  46. package/dist/index.d.ts +1 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +2 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/types/quic.d.ts +518 -0
  51. package/dist/types/quic.d.ts.map +1 -0
  52. package/dist/types/quic.js +272 -0
  53. package/dist/types/quic.js.map +1 -0
  54. package/package.json +11 -3
  55. package/src/browser-entry.js +41 -6
  56. package/src/cli/agentdb-cli.ts +1114 -33
  57. package/src/controllers/ContextSynthesizer.ts +285 -0
  58. package/src/controllers/HNSWIndex.ts +495 -0
  59. package/src/controllers/MMRDiversityRanker.ts +187 -0
  60. package/src/controllers/MetadataFilter.ts +280 -0
  61. package/src/controllers/QUICClient.ts +413 -0
  62. package/src/controllers/QUICServer.ts +498 -0
  63. package/src/controllers/SyncCoordinator.ts +597 -0
  64. package/src/controllers/WASMVectorSearch.ts +11 -2
  65. package/src/controllers/index.ts +14 -0
  66. package/src/examples/quic-sync-example.ts +198 -0
  67. package/src/index.ts +2 -1
  68. package/src/types/quic.ts +772 -0
@@ -0,0 +1,772 @@
1
+ /**
2
+ * QUIC Synchronization Types for AgentDB
3
+ *
4
+ * This file contains TypeScript interfaces and types for the QUIC-based
5
+ * multi-node synchronization system. These types mirror the Protocol Buffer
6
+ * definitions and provide type safety for the sync implementation.
7
+ */
8
+
9
+ // ============================================================================
10
+ // Core Sync Types
11
+ // ============================================================================
12
+
13
+ /**
14
+ * Vector clock for causal ordering of events across distributed nodes.
15
+ * Maps node IDs to their logical clock values.
16
+ */
17
+ export interface VectorClock {
18
+ clocks: Map<string, number>; // node_id -> logical_clock
19
+ }
20
+
21
+ /**
22
+ * Compares two vector clocks to determine causal relationship
23
+ */
24
+ export type VectorClockComparison =
25
+ | 'before' // local happened before remote
26
+ | 'after' // local happened after remote
27
+ | 'concurrent' // concurrent events (conflict)
28
+ | 'equal'; // identical clocks
29
+
30
+ /**
31
+ * Main sync message envelope wrapping all sync operations
32
+ */
33
+ export interface SyncMessage {
34
+ sequenceNumber: number;
35
+ timestampMs: number;
36
+ nodeId: string;
37
+ vectorClock: VectorClock;
38
+ payload: SyncPayload;
39
+ }
40
+
41
+ /**
42
+ * Union type for different sync payload types
43
+ */
44
+ export type SyncPayload =
45
+ | { type: 'episode_sync'; data: EpisodeSync }
46
+ | { type: 'skill_sync'; data: SkillSync }
47
+ | { type: 'causal_edge_sync'; data: CausalEdgeSync }
48
+ | { type: 'reconciliation_request'; data: FullReconciliationRequest }
49
+ | { type: 'reconciliation_response'; data: FullReconciliationResponse };
50
+
51
+ // ============================================================================
52
+ // Episode Synchronization
53
+ // ============================================================================
54
+
55
+ /**
56
+ * Supported operations for episode sync
57
+ */
58
+ export enum EpisodeSyncOperation {
59
+ CREATE = 'CREATE',
60
+ UPDATE = 'UPDATE',
61
+ DELETE = 'DELETE'
62
+ }
63
+
64
+ /**
65
+ * Episode synchronization message
66
+ */
67
+ export interface EpisodeSync {
68
+ operation: EpisodeSyncOperation;
69
+ episodeId: number;
70
+ episodeData: SyncableEpisode;
71
+ causalClock: VectorClock;
72
+ signature: Uint8Array; // HMAC for integrity verification
73
+ }
74
+
75
+ /**
76
+ * Serializable episode data for sync
77
+ */
78
+ export interface SyncableEpisode {
79
+ id?: number;
80
+ agentId: string;
81
+ sessionId: string;
82
+ task: string;
83
+ input: string;
84
+ output: string;
85
+ critique?: string;
86
+ reward: number;
87
+ success: boolean;
88
+ latencyMs: number;
89
+ timestamp: number;
90
+ metadata?: Record<string, any>;
91
+ vectorClock: VectorClock;
92
+ }
93
+
94
+ // ============================================================================
95
+ // Skill Synchronization (CRDT-based)
96
+ // ============================================================================
97
+
98
+ /**
99
+ * G-Counter (Grow-only Counter) for skill usage tracking
100
+ */
101
+ export interface GCounter {
102
+ nodeCounters: Map<string, number>; // node_id -> local_count
103
+ }
104
+
105
+ /**
106
+ * LWW-Register (Last-Write-Wins Register) for scalar values
107
+ */
108
+ export interface LWWRegister<T> {
109
+ value: T;
110
+ timestamp: number;
111
+ nodeId: string;
112
+ }
113
+
114
+ /**
115
+ * OR-Set (Observed-Remove Set) for set-based values
116
+ */
117
+ export interface ORSet<T> {
118
+ adds: Map<T, Set<string>>; // element -> set of unique tags
119
+ removes: Set<string>; // set of removed tags
120
+ }
121
+
122
+ /**
123
+ * Skill synchronization message with CRDT fields
124
+ */
125
+ export interface SkillSync {
126
+ skillId: number;
127
+ skillName: string;
128
+ description?: string;
129
+
130
+ // CRDT fields
131
+ uses: GCounter; // Total uses across nodes
132
+ successRate: LWWRegister<number>; // Success rate with timestamp
133
+ avgReward: LWWRegister<number>; // Average reward with timestamp
134
+ avgLatencyMs: LWWRegister<number>; // Average latency with timestamp
135
+ sourceEpisodes: ORSet<number>; // Set of source episode IDs
136
+
137
+ // Metadata
138
+ signature: Record<string, any>; // Skill signature (inputs/outputs)
139
+ version: VectorClock;
140
+ metadata?: Record<string, any>;
141
+ }
142
+
143
+ // ============================================================================
144
+ // Causal Edge Synchronization
145
+ // ============================================================================
146
+
147
+ /**
148
+ * Metadata for conflict resolution in causal edges
149
+ */
150
+ export interface ConflictResolutionMetadata {
151
+ experimentIds?: number[];
152
+ evidenceCount: number;
153
+ lastModifiedBy: string;
154
+ lastModifiedAt: number;
155
+ }
156
+
157
+ /**
158
+ * Causal edge synchronization message
159
+ */
160
+ export interface CausalEdgeSync {
161
+ edgeId: number;
162
+ fromMemoryId: number;
163
+ fromMemoryType: 'episode' | 'skill' | 'note' | 'fact';
164
+ toMemoryId: number;
165
+ toMemoryType: 'episode' | 'skill' | 'note' | 'fact';
166
+
167
+ // Causal metrics
168
+ similarity: number;
169
+ uplift?: number;
170
+ confidence: number;
171
+ sampleSize?: number;
172
+
173
+ // Evidence and explanation
174
+ evidenceIds?: number[];
175
+ experimentIds?: number[];
176
+ confounderScore?: number;
177
+ mechanism?: string;
178
+
179
+ // Sync metadata
180
+ version: VectorClock;
181
+ conflictMetadata: ConflictResolutionMetadata;
182
+ metadata?: Record<string, any>;
183
+ }
184
+
185
+ // ============================================================================
186
+ // Full Reconciliation
187
+ // ============================================================================
188
+
189
+ /**
190
+ * Data types that can be reconciled
191
+ */
192
+ export type ReconciliableDataType = 'episodes' | 'skills' | 'edges' | 'experiments';
193
+
194
+ /**
195
+ * Request for full reconciliation
196
+ */
197
+ export interface FullReconciliationRequest {
198
+ lastSyncTimestamp: number;
199
+ currentState: VectorClock;
200
+ dataTypes: ReconciliableDataType[];
201
+ requestId: string;
202
+ }
203
+
204
+ /**
205
+ * Response with full state for reconciliation
206
+ */
207
+ export interface FullReconciliationResponse {
208
+ requestId: string;
209
+ episodes: EpisodeSync[];
210
+ skills: SkillSync[];
211
+ edges: CausalEdgeSync[];
212
+ authoritativeClock: VectorClock;
213
+ merkleRoot: string; // For verification
214
+ }
215
+
216
+ /**
217
+ * State summary for efficient reconciliation
218
+ */
219
+ export interface StateSummary {
220
+ episodes: {
221
+ count: number;
222
+ merkleRoot: string;
223
+ vectorClock: VectorClock;
224
+ };
225
+ skills: {
226
+ count: number;
227
+ merkleRoot: string;
228
+ vectorClock: VectorClock;
229
+ };
230
+ edges: {
231
+ count: number;
232
+ merkleRoot: string;
233
+ vectorClock: VectorClock;
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Reconciliation report with results
239
+ */
240
+ export interface ReconciliationReport {
241
+ success: boolean;
242
+ startTime: number;
243
+ endTime: number;
244
+ duration: number;
245
+ recordsAdded: number;
246
+ recordsUpdated: number;
247
+ recordsDeleted: number;
248
+ conflictsResolved: number;
249
+ conflictsUnresolved: number;
250
+ errors: string[];
251
+ }
252
+
253
+ // ============================================================================
254
+ // Authentication & Authorization
255
+ // ============================================================================
256
+
257
+ /**
258
+ * JWT claims for API authorization
259
+ */
260
+ export interface JWTClaims {
261
+ iss: string; // Issuer
262
+ sub: string; // Subject (node ID)
263
+ exp: number; // Expiration timestamp
264
+ iat: number; // Issued at timestamp
265
+ roles: UserRole[];
266
+ scopes: AuthScope[];
267
+ networkId: string;
268
+ metadata?: Record<string, any>;
269
+ }
270
+
271
+ /**
272
+ * User roles
273
+ */
274
+ export enum UserRole {
275
+ ADMIN = 'admin',
276
+ AGENT = 'agent',
277
+ OBSERVER = 'observer',
278
+ LEARNER = 'learner'
279
+ }
280
+
281
+ /**
282
+ * Authorization scopes
283
+ */
284
+ export type AuthScope =
285
+ | 'episodes:read'
286
+ | 'episodes:write'
287
+ | 'episodes:delete'
288
+ | 'skills:read'
289
+ | 'skills:write'
290
+ | 'skills:delete'
291
+ | 'edges:read'
292
+ | 'edges:write'
293
+ | 'edges:delete'
294
+ | 'experiments:read'
295
+ | 'experiments:write'
296
+ | 'reconciliation:request';
297
+
298
+ /**
299
+ * Node registration data
300
+ */
301
+ export interface NodeRegistration {
302
+ nodeId: string;
303
+ certificate: string; // PEM-encoded X.509 certificate
304
+ publicKey: string; // PEM-encoded public key
305
+ networkId: string;
306
+ registeredAt: number;
307
+ expiresAt: number;
308
+ }
309
+
310
+ // ============================================================================
311
+ // Configuration
312
+ // ============================================================================
313
+
314
+ /**
315
+ * Network topology types
316
+ */
317
+ export enum NetworkTopology {
318
+ HUB_AND_SPOKE = 'hub_and_spoke',
319
+ MESH = 'mesh',
320
+ HIERARCHICAL = 'hierarchical'
321
+ }
322
+
323
+ /**
324
+ * Conflict resolution strategies
325
+ */
326
+ export enum ConflictResolutionStrategy {
327
+ AUTO = 'auto', // Automatic resolution using configured algorithms
328
+ MANUAL = 'manual', // Flag conflicts for manual resolution
329
+ INTERACTIVE = 'interactive' // Prompt user for resolution
330
+ }
331
+
332
+ /**
333
+ * Sync mode
334
+ */
335
+ export enum SyncMode {
336
+ INCREMENTAL = 'incremental',
337
+ FULL = 'full',
338
+ HYBRID = 'hybrid'
339
+ }
340
+
341
+ /**
342
+ * Server configuration
343
+ */
344
+ export interface ServerConfig {
345
+ port: number;
346
+ host: string;
347
+ maxConnections: number;
348
+ maxStreamsPerConnection: number;
349
+
350
+ // TLS/Security
351
+ tlsCertPath: string;
352
+ tlsKeyPath: string;
353
+ caCertPath: string;
354
+ jwtSecret: string;
355
+ jwtExpirationMs: number;
356
+
357
+ // Sync settings
358
+ changelogRetentionDays: number;
359
+ changelogMaxRecords: number;
360
+ reconciliationIntervalMs: number;
361
+
362
+ // Performance
363
+ batchSize: number;
364
+ compressionThreshold: number;
365
+ maxMemoryPerConnection: number;
366
+
367
+ // Topology
368
+ topology: NetworkTopology;
369
+ networkId: string;
370
+ }
371
+
372
+ /**
373
+ * Client configuration
374
+ */
375
+ export interface ClientConfig {
376
+ nodeId: string;
377
+ serverUrl: string;
378
+
379
+ // TLS/Security
380
+ clientCertPath: string;
381
+ clientKeyPath: string;
382
+ caCertPath: string;
383
+ jwt: string;
384
+
385
+ // Sync settings
386
+ mode: SyncMode;
387
+ incrementalIntervalMs: number;
388
+ fullReconciliationIntervalMs: number;
389
+ autoSync: boolean;
390
+
391
+ // Conflict resolution
392
+ conflictResolutionStrategy: ConflictResolutionStrategy;
393
+
394
+ // Performance
395
+ batchSize: number;
396
+ compressionThreshold: number;
397
+ retryMaxAttempts: number;
398
+ retryBackoffMs: number;
399
+ }
400
+
401
+ // ============================================================================
402
+ // Status & Monitoring
403
+ // ============================================================================
404
+
405
+ /**
406
+ * Server status
407
+ */
408
+ export interface ServerStatus {
409
+ uptime: number;
410
+ activeConnections: number;
411
+ totalConnectionsHandled: number;
412
+ activeStreams: number;
413
+ changelogSize: number;
414
+ lastReconciliation: number;
415
+
416
+ // Performance metrics
417
+ avgSyncLatencyMs: number;
418
+ throughputBytesPerSec: number;
419
+ conflictsPerMinute: number;
420
+
421
+ // Resource usage
422
+ cpuUsagePercent: number;
423
+ memoryUsageBytes: number;
424
+ diskUsageBytes: number;
425
+ }
426
+
427
+ /**
428
+ * Client status
429
+ */
430
+ export interface ClientStatus {
431
+ connected: boolean;
432
+ nodeId: string;
433
+ serverUrl: string;
434
+ lastSyncTimestamp: number;
435
+ nextSyncScheduled: number;
436
+
437
+ // Sync stats
438
+ episodesSynced: number;
439
+ skillsSynced: number;
440
+ edgesSynced: number;
441
+ conflictsEncountered: number;
442
+ conflictsAutoResolved: number;
443
+
444
+ // Connection health
445
+ connectionUptimeMs: number;
446
+ reconnectAttempts: number;
447
+ lastError?: string;
448
+ }
449
+
450
+ /**
451
+ * Sync result for a single operation
452
+ */
453
+ export interface SyncResult {
454
+ success: boolean;
455
+ duration: number;
456
+
457
+ // Changes applied
458
+ episodesAdded: number;
459
+ episodesUpdated: number;
460
+ episodesDeleted: number;
461
+ skillsAdded: number;
462
+ skillsUpdated: number;
463
+ edgesAdded: number;
464
+ edgesUpdated: number;
465
+
466
+ // Conflicts
467
+ conflictsTotal: number;
468
+ conflictsAutoResolved: number;
469
+ conflictsPending: number;
470
+
471
+ // Errors
472
+ errors: SyncError[];
473
+ }
474
+
475
+ /**
476
+ * Sync error details
477
+ */
478
+ export interface SyncError {
479
+ code: string;
480
+ message: string;
481
+ dataType: ReconciliableDataType;
482
+ recordId?: number;
483
+ timestamp: number;
484
+ retryable: boolean;
485
+ }
486
+
487
+ // ============================================================================
488
+ // Helper Functions (Type Guards & Utilities)
489
+ // ============================================================================
490
+
491
+ /**
492
+ * Type guard for episode sync
493
+ */
494
+ export function isEpisodeSync(payload: SyncPayload): payload is { type: 'episode_sync'; data: EpisodeSync } {
495
+ return payload.type === 'episode_sync';
496
+ }
497
+
498
+ /**
499
+ * Type guard for skill sync
500
+ */
501
+ export function isSkillSync(payload: SyncPayload): payload is { type: 'skill_sync'; data: SkillSync } {
502
+ return payload.type === 'skill_sync';
503
+ }
504
+
505
+ /**
506
+ * Type guard for causal edge sync
507
+ */
508
+ export function isCausalEdgeSync(payload: SyncPayload): payload is { type: 'causal_edge_sync'; data: CausalEdgeSync } {
509
+ return payload.type === 'causal_edge_sync';
510
+ }
511
+
512
+ /**
513
+ * Compare two vector clocks
514
+ */
515
+ export function compareVectorClocks(a: VectorClock, b: VectorClock): VectorClockComparison {
516
+ const allNodes = new Set([...a.clocks.keys(), ...b.clocks.keys()]);
517
+
518
+ let aGreater = false;
519
+ let bGreater = false;
520
+
521
+ for (const node of allNodes) {
522
+ const aClock = a.clocks.get(node) || 0;
523
+ const bClock = b.clocks.get(node) || 0;
524
+
525
+ if (aClock > bClock) aGreater = true;
526
+ if (bClock > aClock) bGreater = true;
527
+ }
528
+
529
+ if (aGreater && !bGreater) return 'after';
530
+ if (bGreater && !aGreater) return 'before';
531
+ if (!aGreater && !bGreater) return 'equal';
532
+ return 'concurrent';
533
+ }
534
+
535
+ /**
536
+ * Merge two vector clocks (take max of each node)
537
+ */
538
+ export function mergeVectorClocks(a: VectorClock, b: VectorClock): VectorClock {
539
+ const merged = new Map(a.clocks);
540
+
541
+ for (const [node, clock] of b.clocks) {
542
+ const existingClock = merged.get(node) || 0;
543
+ merged.set(node, Math.max(existingClock, clock));
544
+ }
545
+
546
+ return { clocks: merged };
547
+ }
548
+
549
+ /**
550
+ * Increment vector clock for local node
551
+ */
552
+ export function incrementVectorClock(clock: VectorClock, nodeId: string): VectorClock {
553
+ const newClocks = new Map(clock.clocks);
554
+ const currentClock = newClocks.get(nodeId) || 0;
555
+ newClocks.set(nodeId, currentClock + 1);
556
+ return { clocks: newClocks };
557
+ }
558
+
559
+ /**
560
+ * Create empty vector clock
561
+ */
562
+ export function createVectorClock(): VectorClock {
563
+ return { clocks: new Map() };
564
+ }
565
+
566
+ // ============================================================================
567
+ // CRDT Operations
568
+ // ============================================================================
569
+
570
+ /**
571
+ * Increment G-Counter for a node
572
+ */
573
+ export function incrementGCounter(counter: GCounter, nodeId: string, delta: number = 1): GCounter {
574
+ const newCounters = new Map(counter.nodeCounters);
575
+ const current = newCounters.get(nodeId) || 0;
576
+ newCounters.set(nodeId, current + delta);
577
+ return { nodeCounters: newCounters };
578
+ }
579
+
580
+ /**
581
+ * Get total value of G-Counter
582
+ */
583
+ export function getGCounterValue(counter: GCounter): number {
584
+ return Array.from(counter.nodeCounters.values()).reduce((sum, count) => sum + count, 0);
585
+ }
586
+
587
+ /**
588
+ * Merge two G-Counters (take max per node)
589
+ */
590
+ export function mergeGCounter(a: GCounter, b: GCounter): GCounter {
591
+ const merged = new Map(a.nodeCounters);
592
+
593
+ for (const [nodeId, count] of b.nodeCounters) {
594
+ const existingCount = merged.get(nodeId) || 0;
595
+ merged.set(nodeId, Math.max(existingCount, count));
596
+ }
597
+
598
+ return { nodeCounters: merged };
599
+ }
600
+
601
+ /**
602
+ * Update LWW-Register with new value
603
+ */
604
+ export function updateLWWRegister<T>(
605
+ register: LWWRegister<T>,
606
+ newValue: T,
607
+ nodeId: string,
608
+ timestamp: number = Date.now()
609
+ ): LWWRegister<T> {
610
+ if (timestamp > register.timestamp ||
611
+ (timestamp === register.timestamp && nodeId > register.nodeId)) {
612
+ return { value: newValue, timestamp, nodeId };
613
+ }
614
+ return register;
615
+ }
616
+
617
+ /**
618
+ * Merge two LWW-Registers (keep most recent)
619
+ */
620
+ export function mergeLWWRegister<T>(a: LWWRegister<T>, b: LWWRegister<T>): LWWRegister<T> {
621
+ if (b.timestamp > a.timestamp) {
622
+ return b;
623
+ } else if (b.timestamp === a.timestamp) {
624
+ return b.nodeId > a.nodeId ? b : a;
625
+ }
626
+ return a;
627
+ }
628
+
629
+ /**
630
+ * Add element to OR-Set
631
+ */
632
+ export function addToORSet<T>(set: ORSet<T>, element: T, uniqueTag: string): ORSet<T> {
633
+ const newAdds = new Map(set.adds);
634
+ if (!newAdds.has(element)) {
635
+ newAdds.set(element, new Set());
636
+ }
637
+ newAdds.get(element)!.add(uniqueTag);
638
+
639
+ return { adds: newAdds, removes: set.removes };
640
+ }
641
+
642
+ /**
643
+ * Remove element from OR-Set
644
+ */
645
+ export function removeFromORSet<T>(set: ORSet<T>, element: T): ORSet<T> {
646
+ const tags = set.adds.get(element);
647
+ if (!tags) return set;
648
+
649
+ const newRemoves = new Set(set.removes);
650
+ tags.forEach(tag => newRemoves.add(tag));
651
+
652
+ return { adds: set.adds, removes: newRemoves };
653
+ }
654
+
655
+ /**
656
+ * Get current elements in OR-Set
657
+ */
658
+ export function getORSetElements<T>(set: ORSet<T>): Set<T> {
659
+ const elements = new Set<T>();
660
+
661
+ for (const [element, tags] of set.adds) {
662
+ // Check if any tag is not in removes
663
+ for (const tag of tags) {
664
+ if (!set.removes.has(tag)) {
665
+ elements.add(element);
666
+ break;
667
+ }
668
+ }
669
+ }
670
+
671
+ return elements;
672
+ }
673
+
674
+ /**
675
+ * Merge two OR-Sets
676
+ */
677
+ export function mergeORSet<T>(a: ORSet<T>, b: ORSet<T>): ORSet<T> {
678
+ const mergedAdds = new Map<T, Set<string>>();
679
+ const mergedRemoves = new Set([...a.removes, ...b.removes]);
680
+
681
+ // Merge adds from both sets
682
+ const allElements = new Set([...a.adds.keys(), ...b.adds.keys()]);
683
+
684
+ for (const element of allElements) {
685
+ const aTags = a.adds.get(element) || new Set();
686
+ const bTags = b.adds.get(element) || new Set();
687
+ const mergedTags = new Set([...aTags, ...bTags]);
688
+
689
+ // Remove tags that are in removes set
690
+ for (const tag of mergedTags) {
691
+ if (mergedRemoves.has(tag)) {
692
+ mergedTags.delete(tag);
693
+ }
694
+ }
695
+
696
+ if (mergedTags.size > 0) {
697
+ mergedAdds.set(element, mergedTags);
698
+ }
699
+ }
700
+
701
+ return { adds: mergedAdds, removes: mergedRemoves };
702
+ }
703
+
704
+ // ============================================================================
705
+ // Conflict Resolution Helpers
706
+ // ============================================================================
707
+
708
+ /**
709
+ * Weighted average for numeric conflict resolution
710
+ */
711
+ export function weightedAverage(v1: number, w1: number, v2: number, w2: number): number {
712
+ if (w1 + w2 === 0) return 0;
713
+ return (v1 * w1 + v2 * w2) / (w1 + w2);
714
+ }
715
+
716
+ /**
717
+ * Determine authorization for operation
718
+ */
719
+ export function isAuthorized(jwt: JWTClaims, requiredScope: AuthScope): boolean {
720
+ return jwt.scopes.includes(requiredScope);
721
+ }
722
+
723
+ /**
724
+ * Check if JWT is expired
725
+ */
726
+ export function isJWTExpired(jwt: JWTClaims): boolean {
727
+ return Date.now() >= jwt.exp * 1000;
728
+ }
729
+
730
+ /**
731
+ * Generate unique tag for OR-Set operations
732
+ */
733
+ export function generateUniqueTag(nodeId: string, timestamp: number = Date.now()): string {
734
+ return `${nodeId}-${timestamp}-${Math.random().toString(36).substr(2, 9)}`;
735
+ }
736
+
737
+ // ============================================================================
738
+ // Event Types for Client SDK
739
+ // ============================================================================
740
+
741
+ /**
742
+ * Events emitted by sync client
743
+ */
744
+ export type SyncEvent =
745
+ | { type: 'sync_started'; timestamp: number }
746
+ | { type: 'sync_completed'; result: SyncResult }
747
+ | { type: 'sync_failed'; error: SyncError }
748
+ | { type: 'conflict_detected'; conflict: ConflictData }
749
+ | { type: 'conflict_resolved'; conflict: ConflictData; resolution: any }
750
+ | { type: 'connection_established'; nodeId: string; serverUrl: string }
751
+ | { type: 'connection_lost'; reason: string }
752
+ | { type: 'reconnecting'; attempt: number }
753
+ | { type: 'reconciliation_started'; requestId: string }
754
+ | { type: 'reconciliation_completed'; report: ReconciliationReport };
755
+
756
+ /**
757
+ * Conflict data for manual resolution
758
+ */
759
+ export interface ConflictData {
760
+ dataType: ReconciliableDataType;
761
+ recordId: number;
762
+ localVersion: any;
763
+ remoteVersion: any;
764
+ localVectorClock: VectorClock;
765
+ remoteVectorClock: VectorClock;
766
+ detectedAt: number;
767
+ }
768
+
769
+ /**
770
+ * Event handler type
771
+ */
772
+ export type SyncEventHandler = (event: SyncEvent) => void;