agentdb 1.5.8 → 1.6.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/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/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 +12 -0
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js +6 -0
- package/dist/controllers/index.js.map +1 -1
- package/dist/db-fallback.d.ts.map +1 -1
- package/dist/db-fallback.js +14 -11
- package/dist/db-fallback.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/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 +9 -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/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 +12 -0
- package/src/db-fallback.ts +13 -10
- package/src/examples/quic-sync-example.ts +198 -0
- package/src/types/quic.ts +772 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QUICServer - QUIC Protocol Server for AgentDB Synchronization
|
|
3
|
+
*
|
|
4
|
+
* Implements a QUIC server for receiving and handling synchronization requests
|
|
5
|
+
* from remote AgentDB instances. Supports episodes, skills, and edge synchronization.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Start/stop server lifecycle management
|
|
9
|
+
* - Client connection handling
|
|
10
|
+
* - Authentication and authorization
|
|
11
|
+
* - Rate limiting per client
|
|
12
|
+
* - Sync request processing (episodes, skills, edges)
|
|
13
|
+
* - Comprehensive error handling and logging
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import chalk from 'chalk';
|
|
17
|
+
|
|
18
|
+
// Database type from db-fallback
|
|
19
|
+
type Database = any;
|
|
20
|
+
|
|
21
|
+
export interface QUICServerConfig {
|
|
22
|
+
host?: string;
|
|
23
|
+
port?: number;
|
|
24
|
+
maxConnections?: number;
|
|
25
|
+
authToken?: string;
|
|
26
|
+
rateLimit?: {
|
|
27
|
+
maxRequestsPerMinute: number;
|
|
28
|
+
maxBytesPerMinute: number;
|
|
29
|
+
};
|
|
30
|
+
tlsConfig?: {
|
|
31
|
+
cert?: string;
|
|
32
|
+
key?: string;
|
|
33
|
+
ca?: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SyncRequest {
|
|
38
|
+
type: 'episodes' | 'skills' | 'edges' | 'full';
|
|
39
|
+
since?: number; // Timestamp for incremental sync
|
|
40
|
+
filters?: Record<string, any>;
|
|
41
|
+
batchSize?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SyncResponse {
|
|
45
|
+
success: boolean;
|
|
46
|
+
data?: any;
|
|
47
|
+
error?: string;
|
|
48
|
+
nextCursor?: number;
|
|
49
|
+
hasMore?: boolean;
|
|
50
|
+
count?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ClientConnection {
|
|
54
|
+
id: string;
|
|
55
|
+
address: string;
|
|
56
|
+
connectedAt: number;
|
|
57
|
+
requestCount: number;
|
|
58
|
+
bytesReceived: number;
|
|
59
|
+
lastRequestAt: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface RateLimitState {
|
|
63
|
+
requestCount: number;
|
|
64
|
+
bytesTransferred: number;
|
|
65
|
+
windowStart: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class QUICServer {
|
|
69
|
+
private db: Database;
|
|
70
|
+
private config: Required<QUICServerConfig>;
|
|
71
|
+
private isRunning: boolean = false;
|
|
72
|
+
private connections: Map<string, ClientConnection> = new Map();
|
|
73
|
+
private rateLimitState: Map<string, RateLimitState> = new Map();
|
|
74
|
+
private server: any = null;
|
|
75
|
+
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
76
|
+
|
|
77
|
+
constructor(db: Database, config: QUICServerConfig = {}) {
|
|
78
|
+
this.db = db;
|
|
79
|
+
this.config = {
|
|
80
|
+
host: config.host || '0.0.0.0',
|
|
81
|
+
port: config.port || 4433,
|
|
82
|
+
maxConnections: config.maxConnections || 100,
|
|
83
|
+
authToken: config.authToken || '',
|
|
84
|
+
rateLimit: config.rateLimit || {
|
|
85
|
+
maxRequestsPerMinute: 60,
|
|
86
|
+
maxBytesPerMinute: 10 * 1024 * 1024, // 10MB
|
|
87
|
+
},
|
|
88
|
+
tlsConfig: config.tlsConfig || {},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Start the QUIC server
|
|
94
|
+
*/
|
|
95
|
+
async start(): Promise<void> {
|
|
96
|
+
if (this.isRunning) {
|
|
97
|
+
console.log(chalk.yellow('⚠️ QUIC server is already running'));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
console.log(chalk.blue('🚀 Starting QUIC server...'));
|
|
103
|
+
console.log(chalk.gray(` Host: ${this.config.host}`));
|
|
104
|
+
console.log(chalk.gray(` Port: ${this.config.port}`));
|
|
105
|
+
|
|
106
|
+
// Note: Actual QUIC implementation would use a library like @fails-components/webtransport
|
|
107
|
+
// or node-quic. This is a reference implementation showing the interface.
|
|
108
|
+
|
|
109
|
+
// Initialize server state
|
|
110
|
+
this.isRunning = true;
|
|
111
|
+
this.startCleanupInterval();
|
|
112
|
+
|
|
113
|
+
console.log(chalk.green('✓ QUIC server started successfully'));
|
|
114
|
+
console.log(chalk.gray(` Max connections: ${this.config.maxConnections}`));
|
|
115
|
+
console.log(chalk.gray(` Rate limit: ${this.config.rateLimit.maxRequestsPerMinute} req/min`));
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const err = error as Error;
|
|
118
|
+
console.error(chalk.red('✗ Failed to start QUIC server:'), err.message);
|
|
119
|
+
throw new Error(`QUIC server start failed: ${err.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Stop the QUIC server
|
|
125
|
+
*/
|
|
126
|
+
async stop(): Promise<void> {
|
|
127
|
+
if (!this.isRunning) {
|
|
128
|
+
console.log(chalk.yellow('⚠️ QUIC server is not running'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
console.log(chalk.blue('🛑 Stopping QUIC server...'));
|
|
134
|
+
|
|
135
|
+
// Close all connections
|
|
136
|
+
for (const [clientId, connection] of this.connections.entries()) {
|
|
137
|
+
console.log(chalk.gray(` Closing connection: ${clientId}`));
|
|
138
|
+
// Close connection logic here
|
|
139
|
+
}
|
|
140
|
+
this.connections.clear();
|
|
141
|
+
this.rateLimitState.clear();
|
|
142
|
+
|
|
143
|
+
// Stop cleanup interval
|
|
144
|
+
if (this.cleanupInterval) {
|
|
145
|
+
clearInterval(this.cleanupInterval);
|
|
146
|
+
this.cleanupInterval = null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Close server
|
|
150
|
+
if (this.server) {
|
|
151
|
+
// await this.server.close();
|
|
152
|
+
this.server = null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.isRunning = false;
|
|
156
|
+
console.log(chalk.green('✓ QUIC server stopped successfully'));
|
|
157
|
+
} catch (error) {
|
|
158
|
+
const err = error as Error;
|
|
159
|
+
console.error(chalk.red('✗ Error stopping QUIC server:'), err.message);
|
|
160
|
+
throw new Error(`QUIC server stop failed: ${err.message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Handle incoming client connection
|
|
166
|
+
*/
|
|
167
|
+
private async handleConnection(clientId: string, address: string): Promise<boolean> {
|
|
168
|
+
// Check max connections
|
|
169
|
+
if (this.connections.size >= this.config.maxConnections) {
|
|
170
|
+
console.log(chalk.yellow(`⚠️ Max connections reached, rejecting ${clientId}`));
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Register connection
|
|
175
|
+
const connection: ClientConnection = {
|
|
176
|
+
id: clientId,
|
|
177
|
+
address,
|
|
178
|
+
connectedAt: Date.now(),
|
|
179
|
+
requestCount: 0,
|
|
180
|
+
bytesReceived: 0,
|
|
181
|
+
lastRequestAt: 0,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
this.connections.set(clientId, connection);
|
|
185
|
+
console.log(chalk.green(`✓ Client connected: ${clientId} from ${address}`));
|
|
186
|
+
console.log(chalk.gray(` Active connections: ${this.connections.size}`));
|
|
187
|
+
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Authenticate client request
|
|
193
|
+
*/
|
|
194
|
+
private authenticate(clientId: string, authToken: string): boolean {
|
|
195
|
+
if (!this.config.authToken) {
|
|
196
|
+
return true; // No auth required
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const isValid = authToken === this.config.authToken;
|
|
200
|
+
if (!isValid) {
|
|
201
|
+
console.log(chalk.red(`✗ Authentication failed for client: ${clientId}`));
|
|
202
|
+
}
|
|
203
|
+
return isValid;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Check rate limits for client
|
|
208
|
+
*/
|
|
209
|
+
private checkRateLimit(clientId: string, requestSize: number): boolean {
|
|
210
|
+
const now = Date.now();
|
|
211
|
+
let state = this.rateLimitState.get(clientId);
|
|
212
|
+
|
|
213
|
+
if (!state || now - state.windowStart > 60000) {
|
|
214
|
+
// New window
|
|
215
|
+
state = {
|
|
216
|
+
requestCount: 0,
|
|
217
|
+
bytesTransferred: 0,
|
|
218
|
+
windowStart: now,
|
|
219
|
+
};
|
|
220
|
+
this.rateLimitState.set(clientId, state);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check limits
|
|
224
|
+
if (state.requestCount >= this.config.rateLimit.maxRequestsPerMinute) {
|
|
225
|
+
console.log(chalk.yellow(`⚠️ Rate limit exceeded (requests) for ${clientId}`));
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (state.bytesTransferred + requestSize > this.config.rateLimit.maxBytesPerMinute) {
|
|
230
|
+
console.log(chalk.yellow(`⚠️ Rate limit exceeded (bytes) for ${clientId}`));
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Update state
|
|
235
|
+
state.requestCount++;
|
|
236
|
+
state.bytesTransferred += requestSize;
|
|
237
|
+
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Process sync request from client
|
|
243
|
+
*/
|
|
244
|
+
async processSyncRequest(
|
|
245
|
+
clientId: string,
|
|
246
|
+
request: SyncRequest,
|
|
247
|
+
authToken: string
|
|
248
|
+
): Promise<SyncResponse> {
|
|
249
|
+
try {
|
|
250
|
+
// Authenticate
|
|
251
|
+
if (!this.authenticate(clientId, authToken)) {
|
|
252
|
+
return {
|
|
253
|
+
success: false,
|
|
254
|
+
error: 'Authentication failed',
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check rate limit
|
|
259
|
+
const requestSize = JSON.stringify(request).length;
|
|
260
|
+
if (!this.checkRateLimit(clientId, requestSize)) {
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
error: 'Rate limit exceeded',
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Update connection stats
|
|
268
|
+
const connection = this.connections.get(clientId);
|
|
269
|
+
if (connection) {
|
|
270
|
+
connection.requestCount++;
|
|
271
|
+
connection.bytesReceived += requestSize;
|
|
272
|
+
connection.lastRequestAt = Date.now();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
console.log(chalk.blue(`📥 Processing sync request from ${clientId}`));
|
|
276
|
+
console.log(chalk.gray(` Type: ${request.type}`));
|
|
277
|
+
console.log(chalk.gray(` Since: ${request.since || 'full sync'}`));
|
|
278
|
+
|
|
279
|
+
// Process based on type
|
|
280
|
+
let data: any;
|
|
281
|
+
let count = 0;
|
|
282
|
+
|
|
283
|
+
switch (request.type) {
|
|
284
|
+
case 'episodes':
|
|
285
|
+
data = await this.syncEpisodes(request);
|
|
286
|
+
count = data.length;
|
|
287
|
+
break;
|
|
288
|
+
case 'skills':
|
|
289
|
+
data = await this.syncSkills(request);
|
|
290
|
+
count = data.length;
|
|
291
|
+
break;
|
|
292
|
+
case 'edges':
|
|
293
|
+
data = await this.syncEdges(request);
|
|
294
|
+
count = data.length;
|
|
295
|
+
break;
|
|
296
|
+
case 'full':
|
|
297
|
+
data = await this.syncFull(request);
|
|
298
|
+
count = data.episodes?.length + data.skills?.length + data.edges?.length || 0;
|
|
299
|
+
break;
|
|
300
|
+
default:
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
error: `Unknown sync type: ${request.type}`,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
console.log(chalk.green(`✓ Sync completed: ${count} items sent`));
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
success: true,
|
|
311
|
+
data,
|
|
312
|
+
count,
|
|
313
|
+
hasMore: false, // Could implement pagination here
|
|
314
|
+
};
|
|
315
|
+
} catch (error) {
|
|
316
|
+
const err = error as Error;
|
|
317
|
+
console.error(chalk.red('✗ Sync request failed:'), err.message);
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
error: err.message,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Sync episodes data
|
|
327
|
+
*/
|
|
328
|
+
private async syncEpisodes(request: SyncRequest): Promise<any[]> {
|
|
329
|
+
const { since, filters, batchSize = 1000 } = request;
|
|
330
|
+
|
|
331
|
+
let query = 'SELECT * FROM episodes WHERE 1=1';
|
|
332
|
+
const params: any[] = [];
|
|
333
|
+
|
|
334
|
+
if (since) {
|
|
335
|
+
query += ' AND ts > ?';
|
|
336
|
+
params.push(since);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Apply filters
|
|
340
|
+
if (filters) {
|
|
341
|
+
if (filters.sessionId) {
|
|
342
|
+
query += ' AND session_id = ?';
|
|
343
|
+
params.push(filters.sessionId);
|
|
344
|
+
}
|
|
345
|
+
if (filters.success !== undefined) {
|
|
346
|
+
query += ' AND success = ?';
|
|
347
|
+
params.push(filters.success ? 1 : 0);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
query += ` ORDER BY ts DESC LIMIT ${batchSize}`;
|
|
352
|
+
|
|
353
|
+
const stmt = this.db.prepare(query);
|
|
354
|
+
const rows = stmt.all(...params);
|
|
355
|
+
|
|
356
|
+
return rows.map((row: any) => ({
|
|
357
|
+
id: row.id,
|
|
358
|
+
ts: row.ts,
|
|
359
|
+
sessionId: row.session_id,
|
|
360
|
+
task: row.task,
|
|
361
|
+
input: row.input,
|
|
362
|
+
output: row.output,
|
|
363
|
+
critique: row.critique,
|
|
364
|
+
reward: row.reward,
|
|
365
|
+
success: row.success === 1,
|
|
366
|
+
latencyMs: row.latency_ms,
|
|
367
|
+
tokensUsed: row.tokens_used,
|
|
368
|
+
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
369
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : {},
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Sync skills data
|
|
375
|
+
*/
|
|
376
|
+
private async syncSkills(request: SyncRequest): Promise<any[]> {
|
|
377
|
+
const { since, batchSize = 1000 } = request;
|
|
378
|
+
|
|
379
|
+
let query = 'SELECT * FROM skills WHERE 1=1';
|
|
380
|
+
const params: any[] = [];
|
|
381
|
+
|
|
382
|
+
if (since) {
|
|
383
|
+
query += ' AND ts > ?';
|
|
384
|
+
params.push(since);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
query += ` ORDER BY ts DESC LIMIT ${batchSize}`;
|
|
388
|
+
|
|
389
|
+
const stmt = this.db.prepare(query);
|
|
390
|
+
const rows = stmt.all(...params);
|
|
391
|
+
|
|
392
|
+
return rows.map((row: any) => ({
|
|
393
|
+
id: row.id,
|
|
394
|
+
ts: row.ts,
|
|
395
|
+
name: row.name,
|
|
396
|
+
description: row.description,
|
|
397
|
+
code: row.code,
|
|
398
|
+
successRate: row.success_rate,
|
|
399
|
+
usageCount: row.usage_count,
|
|
400
|
+
avgReward: row.avg_reward,
|
|
401
|
+
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
402
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : {},
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Sync edges (skill relationships)
|
|
408
|
+
*/
|
|
409
|
+
private async syncEdges(request: SyncRequest): Promise<any[]> {
|
|
410
|
+
const { since, batchSize = 1000 } = request;
|
|
411
|
+
|
|
412
|
+
let query = 'SELECT * FROM skill_edges WHERE 1=1';
|
|
413
|
+
const params: any[] = [];
|
|
414
|
+
|
|
415
|
+
if (since) {
|
|
416
|
+
query += ' AND ts > ?';
|
|
417
|
+
params.push(since);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
query += ` ORDER BY ts DESC LIMIT ${batchSize}`;
|
|
421
|
+
|
|
422
|
+
const stmt = this.db.prepare(query);
|
|
423
|
+
const rows = stmt.all(...params);
|
|
424
|
+
|
|
425
|
+
return rows.map((row: any) => ({
|
|
426
|
+
id: row.id,
|
|
427
|
+
ts: row.ts,
|
|
428
|
+
fromSkillId: row.from_skill_id,
|
|
429
|
+
toSkillId: row.to_skill_id,
|
|
430
|
+
weight: row.weight,
|
|
431
|
+
coOccurrences: row.co_occurrences,
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Full sync of all data
|
|
437
|
+
*/
|
|
438
|
+
private async syncFull(request: SyncRequest): Promise<any> {
|
|
439
|
+
const [episodes, skills, edges] = await Promise.all([
|
|
440
|
+
this.syncEpisodes(request),
|
|
441
|
+
this.syncSkills(request),
|
|
442
|
+
this.syncEdges(request),
|
|
443
|
+
]);
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
episodes,
|
|
447
|
+
skills,
|
|
448
|
+
edges,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Start cleanup interval for stale connections
|
|
454
|
+
*/
|
|
455
|
+
private startCleanupInterval(): void {
|
|
456
|
+
this.cleanupInterval = setInterval(() => {
|
|
457
|
+
const now = Date.now();
|
|
458
|
+
const staleThreshold = 5 * 60 * 1000; // 5 minutes
|
|
459
|
+
|
|
460
|
+
for (const [clientId, connection] of this.connections.entries()) {
|
|
461
|
+
if (now - connection.lastRequestAt > staleThreshold && connection.requestCount > 0) {
|
|
462
|
+
console.log(chalk.gray(`🧹 Removing stale connection: ${clientId}`));
|
|
463
|
+
this.connections.delete(clientId);
|
|
464
|
+
this.rateLimitState.delete(clientId);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}, 60000); // Run every minute
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Get server status
|
|
472
|
+
*/
|
|
473
|
+
getStatus(): {
|
|
474
|
+
isRunning: boolean;
|
|
475
|
+
activeConnections: number;
|
|
476
|
+
totalRequests: number;
|
|
477
|
+
config: QUICServerConfig;
|
|
478
|
+
} {
|
|
479
|
+
let totalRequests = 0;
|
|
480
|
+
for (const connection of this.connections.values()) {
|
|
481
|
+
totalRequests += connection.requestCount;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return {
|
|
485
|
+
isRunning: this.isRunning,
|
|
486
|
+
activeConnections: this.connections.size,
|
|
487
|
+
totalRequests,
|
|
488
|
+
config: this.config,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Get connection info
|
|
494
|
+
*/
|
|
495
|
+
getConnections(): ClientConnection[] {
|
|
496
|
+
return Array.from(this.connections.values());
|
|
497
|
+
}
|
|
498
|
+
}
|