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.
- package/README.md +11 -11
- package/dist/agentdb.min.js +4 -4
- package/dist/cli/agentdb-cli.d.ts +29 -0
- package/dist/cli/agentdb-cli.d.ts.map +1 -1
- package/dist/cli/agentdb-cli.js +1009 -34
- package/dist/cli/agentdb-cli.js.map +1 -1
- package/dist/controllers/ContextSynthesizer.d.ts +65 -0
- package/dist/controllers/ContextSynthesizer.d.ts.map +1 -0
- package/dist/controllers/ContextSynthesizer.js +208 -0
- package/dist/controllers/ContextSynthesizer.js.map +1 -0
- package/dist/controllers/HNSWIndex.d.ts +128 -0
- package/dist/controllers/HNSWIndex.d.ts.map +1 -0
- package/dist/controllers/HNSWIndex.js +361 -0
- package/dist/controllers/HNSWIndex.js.map +1 -0
- package/dist/controllers/MMRDiversityRanker.d.ts +50 -0
- package/dist/controllers/MMRDiversityRanker.d.ts.map +1 -0
- package/dist/controllers/MMRDiversityRanker.js +130 -0
- package/dist/controllers/MMRDiversityRanker.js.map +1 -0
- package/dist/controllers/MetadataFilter.d.ts +70 -0
- package/dist/controllers/MetadataFilter.d.ts.map +1 -0
- package/dist/controllers/MetadataFilter.js +243 -0
- package/dist/controllers/MetadataFilter.js.map +1 -0
- package/dist/controllers/QUICClient.d.ts +109 -0
- package/dist/controllers/QUICClient.d.ts.map +1 -0
- package/dist/controllers/QUICClient.js +299 -0
- package/dist/controllers/QUICClient.js.map +1 -0
- package/dist/controllers/QUICServer.d.ts +121 -0
- package/dist/controllers/QUICServer.d.ts.map +1 -0
- package/dist/controllers/QUICServer.js +383 -0
- package/dist/controllers/QUICServer.js.map +1 -0
- package/dist/controllers/SyncCoordinator.d.ts +120 -0
- package/dist/controllers/SyncCoordinator.d.ts.map +1 -0
- package/dist/controllers/SyncCoordinator.js +441 -0
- package/dist/controllers/SyncCoordinator.js.map +1 -0
- package/dist/controllers/WASMVectorSearch.d.ts.map +1 -1
- package/dist/controllers/WASMVectorSearch.js +10 -2
- package/dist/controllers/WASMVectorSearch.js.map +1 -1
- package/dist/controllers/index.d.ts +14 -0
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js +7 -0
- package/dist/controllers/index.js.map +1 -1
- package/dist/examples/quic-sync-example.d.ts +9 -0
- package/dist/examples/quic-sync-example.d.ts.map +1 -0
- package/dist/examples/quic-sync-example.js +169 -0
- package/dist/examples/quic-sync-example.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/types/quic.d.ts +518 -0
- package/dist/types/quic.d.ts.map +1 -0
- package/dist/types/quic.js +272 -0
- package/dist/types/quic.js.map +1 -0
- package/package.json +11 -3
- package/src/browser-entry.js +41 -6
- package/src/cli/agentdb-cli.ts +1114 -33
- package/src/controllers/ContextSynthesizer.ts +285 -0
- package/src/controllers/HNSWIndex.ts +495 -0
- package/src/controllers/MMRDiversityRanker.ts +187 -0
- package/src/controllers/MetadataFilter.ts +280 -0
- package/src/controllers/QUICClient.ts +413 -0
- package/src/controllers/QUICServer.ts +498 -0
- package/src/controllers/SyncCoordinator.ts +597 -0
- package/src/controllers/WASMVectorSearch.ts +11 -2
- package/src/controllers/index.ts +14 -0
- package/src/examples/quic-sync-example.ts +198 -0
- package/src/index.ts +2 -1
- 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;
|