fss-link 1.0.51 → 1.0.53
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 +42 -2
- package/dist/package.json +1 -1
- package/dist/src/config/auth.js +5 -8
- package/dist/src/config/auth.js.map +1 -1
- package/dist/src/config/config.d.ts +1 -0
- package/dist/src/config/config.js +5 -0
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/database-utils.d.ts +185 -0
- package/dist/src/config/database-utils.js +295 -0
- package/dist/src/config/database-utils.js.map +1 -0
- package/dist/src/config/database.d.ts +197 -1
- package/dist/src/config/database.js +499 -160
- package/dist/src/config/database.js.map +1 -1
- package/dist/src/config/databaseHealthMonitor.d.ts +86 -0
- package/dist/src/config/databaseHealthMonitor.js +180 -0
- package/dist/src/config/databaseHealthMonitor.js.map +1 -0
- package/dist/src/config/databaseMetrics.d.ts +147 -0
- package/dist/src/config/databaseMetrics.js +369 -0
- package/dist/src/config/databaseMetrics.js.map +1 -0
- package/dist/src/config/databaseMigrations.js +40 -2
- package/dist/src/config/databaseMigrations.js.map +1 -1
- package/dist/src/config/databaseSchemaValidator.d.ts +114 -0
- package/dist/src/config/databaseSchemaValidator.js +499 -0
- package/dist/src/config/databaseSchemaValidator.js.map +1 -0
- package/dist/src/config/providerPersistence.d.ts +7 -0
- package/dist/src/config/providerPersistence.js +14 -7
- package/dist/src/config/providerPersistence.js.map +1 -1
- package/dist/src/gemini.js +35 -2
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/ui/utils/updateCheck.js +1 -1
- package/dist/src/ui/utils/updateCheck.js.map +1 -1
- package/dist/src/utils/installationInfo.js +16 -0
- package/dist/src/utils/installationInfo.js.map +1 -1
- package/dist/src/utils/installationInfo.test.js +6 -6
- package/dist/src/utils/installationInfo.test.js.map +1 -1
- package/dist/src/validateNonInterActiveAuth.js +18 -4
- package/dist/src/validateNonInterActiveAuth.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -12,6 +12,10 @@ import { getDatabasePool } from './databasePool.js';
|
|
|
12
12
|
import { MigrationManager } from './databaseMigrations.js';
|
|
13
13
|
import { DatabaseBackupManager } from './databaseBackup.js';
|
|
14
14
|
import { QueryOptimizer } from './queryOptimizer.js';
|
|
15
|
+
import { DatabaseMetricsCollector } from './databaseMetrics.js';
|
|
16
|
+
import { DatabaseHealthMonitor } from './databaseHealthMonitor.js';
|
|
17
|
+
import { DatabaseSchemaValidator } from './databaseSchemaValidator.js';
|
|
18
|
+
import { safeQueryFirst, safeQuery, safeExec, safeTransaction, getLastInsertId } from './database-utils.js';
|
|
15
19
|
/**
|
|
16
20
|
* FSS Link Database Manager
|
|
17
21
|
* Handles all persistent state for model configurations and user preferences
|
|
@@ -27,6 +31,9 @@ export class FSSLinkDatabase {
|
|
|
27
31
|
migrationManager;
|
|
28
32
|
backupManager;
|
|
29
33
|
queryOptimizer;
|
|
34
|
+
metricsCollector;
|
|
35
|
+
healthMonitor;
|
|
36
|
+
schemaValidator;
|
|
30
37
|
constructor() {
|
|
31
38
|
// Ensure FSS Link settings directory exists
|
|
32
39
|
if (!fs.existsSync(USER_SETTINGS_DIR)) {
|
|
@@ -38,6 +45,9 @@ export class FSSLinkDatabase {
|
|
|
38
45
|
this.migrationManager = new MigrationManager();
|
|
39
46
|
this.backupManager = new DatabaseBackupManager();
|
|
40
47
|
this.queryOptimizer = new QueryOptimizer();
|
|
48
|
+
this.metricsCollector = new DatabaseMetricsCollector();
|
|
49
|
+
this.healthMonitor = new DatabaseHealthMonitor();
|
|
50
|
+
this.schemaValidator = new DatabaseSchemaValidator();
|
|
41
51
|
}
|
|
42
52
|
/**
|
|
43
53
|
* Derive encryption key from machine-specific data
|
|
@@ -55,7 +65,7 @@ export class FSSLinkDatabase {
|
|
|
55
65
|
if (!apiKey)
|
|
56
66
|
return '';
|
|
57
67
|
const iv = crypto.randomBytes(16);
|
|
58
|
-
const cipher = crypto.
|
|
68
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, iv);
|
|
59
69
|
let encrypted = cipher.update(apiKey, 'utf8', 'hex');
|
|
60
70
|
encrypted += cipher.final('hex');
|
|
61
71
|
return `enc:${iv.toString('hex')}:${encrypted}`;
|
|
@@ -71,8 +81,9 @@ export class FSSLinkDatabase {
|
|
|
71
81
|
const parts = encryptedData.substring(4).split(':'); // Remove 'enc:' prefix
|
|
72
82
|
if (parts.length !== 2)
|
|
73
83
|
return encryptedData;
|
|
74
|
-
const [
|
|
75
|
-
const
|
|
84
|
+
const [ivHex, encrypted] = parts;
|
|
85
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
86
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, iv);
|
|
76
87
|
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
77
88
|
decrypted += decipher.final('utf8');
|
|
78
89
|
return decrypted;
|
|
@@ -88,35 +99,67 @@ export class FSSLinkDatabase {
|
|
|
88
99
|
async initialize() {
|
|
89
100
|
if (this.initialized)
|
|
90
101
|
return;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
this.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
debugLog('Starting FSS Link database initialization...');
|
|
103
|
+
try {
|
|
104
|
+
// Initialize the connection pool
|
|
105
|
+
debugLog('Initializing database connection pool...');
|
|
106
|
+
await this.pool.initialize();
|
|
107
|
+
debugLog('Database connection pool initialized successfully');
|
|
108
|
+
// Use a connection from the pool to run migrations and initialize schema
|
|
109
|
+
await this.pool.withConnection(async (db) => {
|
|
110
|
+
this.db = db; // Keep reference for compatibility
|
|
111
|
+
debugLog('Database connection acquired, checking migration status...');
|
|
112
|
+
// Create auto backup before migrations if database exists
|
|
113
|
+
const needsMigration = this.migrationManager.needsMigration(db);
|
|
114
|
+
debugLog(`Migration needed: ${needsMigration}`);
|
|
115
|
+
if (needsMigration && fs.existsSync(this.dbPath)) {
|
|
116
|
+
try {
|
|
117
|
+
console.log('Creating backup before migration...');
|
|
118
|
+
await this.backupManager.createAutoBackup(db);
|
|
119
|
+
console.log('Pre-migration backup created successfully');
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.warn('Failed to create pre-migration backup:', error);
|
|
123
|
+
}
|
|
102
124
|
}
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
// Run database migrations if needed
|
|
126
|
+
if (needsMigration) {
|
|
127
|
+
debugLog('Running database migrations...');
|
|
128
|
+
await this.migrationManager.migrate(db);
|
|
129
|
+
debugLog('Database migrations completed successfully');
|
|
105
130
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
else {
|
|
132
|
+
debugLog('Database is up to date, no migrations needed');
|
|
133
|
+
}
|
|
134
|
+
// Validate schema integrity
|
|
135
|
+
debugLog('Validating database schema...');
|
|
136
|
+
if (!this.migrationManager.validateSchema(db)) {
|
|
137
|
+
throw new Error('Database schema validation failed');
|
|
138
|
+
}
|
|
139
|
+
debugLog('Database schema validation passed');
|
|
140
|
+
// Run legacy schema initialization for any missed items
|
|
141
|
+
debugLog('Running legacy schema initialization...');
|
|
142
|
+
await this.initializeSchema();
|
|
143
|
+
debugLog('Legacy schema initialization completed');
|
|
144
|
+
// Final verification that critical tables exist
|
|
145
|
+
debugLog('Performing final table existence check...');
|
|
146
|
+
const criticalTables = ['model_configs', 'provider_usage_stats', 'user_preferences'];
|
|
147
|
+
for (const tableName of criticalTables) {
|
|
148
|
+
const tableCheck = db.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`);
|
|
149
|
+
if (tableCheck.length === 0) {
|
|
150
|
+
throw new Error(`Critical table '${tableName}' missing after initialization`);
|
|
151
|
+
}
|
|
152
|
+
debugLog(`✓ Table '${tableName}' exists`);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
this.initialized = true;
|
|
156
|
+
debugLog('FSS Link database initialization completed successfully');
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.error('Database initialization failed:', error);
|
|
160
|
+
this.initialized = false;
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
120
163
|
}
|
|
121
164
|
/**
|
|
122
165
|
* Ensure database is initialized
|
|
@@ -258,6 +301,18 @@ export class FSSLinkDatabase {
|
|
|
258
301
|
UNIQUE(provider_id, setting_key)
|
|
259
302
|
)
|
|
260
303
|
`);
|
|
304
|
+
// Provider usage stats table (safety fallback if migrations failed)
|
|
305
|
+
debugLog('Creating provider_usage_stats table (legacy schema fallback)...');
|
|
306
|
+
this.db.exec(`
|
|
307
|
+
CREATE TABLE IF NOT EXISTS provider_usage_stats (
|
|
308
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
309
|
+
provider_id INTEGER NOT NULL,
|
|
310
|
+
tokens_used INTEGER DEFAULT 0,
|
|
311
|
+
session_duration_seconds INTEGER DEFAULT 0,
|
|
312
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
313
|
+
)
|
|
314
|
+
`);
|
|
315
|
+
debugLog('provider_usage_stats table exists (legacy schema)');
|
|
261
316
|
// Create indexes for performance
|
|
262
317
|
this.db.exec(`
|
|
263
318
|
CREATE INDEX IF NOT EXISTS idx_model_configs_active ON model_configs(is_active);
|
|
@@ -265,7 +320,7 @@ export class FSSLinkDatabase {
|
|
|
265
320
|
CREATE INDEX IF NOT EXISTS idx_model_configs_last_used ON model_configs(last_used DESC);
|
|
266
321
|
CREATE INDEX IF NOT EXISTS idx_model_configs_auth_type ON model_configs(auth_type);
|
|
267
322
|
CREATE INDEX IF NOT EXISTS idx_custom_endpoints_provider ON custom_endpoints(provider_id);
|
|
268
|
-
CREATE INDEX IF NOT EXISTS
|
|
323
|
+
CREATE INDEX IF NOT EXISTS idx_provider_usage_stats_aggregation ON provider_usage_stats(provider_id, created_at DESC);
|
|
269
324
|
CREATE INDEX IF NOT EXISTS idx_provider_settings_key ON provider_settings(provider_id, setting_key);
|
|
270
325
|
`);
|
|
271
326
|
// Insert default configurations if none exist
|
|
@@ -279,10 +334,9 @@ export class FSSLinkDatabase {
|
|
|
279
334
|
async ensureDefaultConfigurations() {
|
|
280
335
|
if (!this.db)
|
|
281
336
|
return;
|
|
282
|
-
// Check if any configurations exist
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
if (countResult.count === 0) {
|
|
337
|
+
// MIGRATED: Check if any configurations exist
|
|
338
|
+
const countResult = safeQueryFirst(this.db, 'SELECT COUNT(*) as count FROM model_configs');
|
|
339
|
+
if ((countResult?.count || 0) === 0) {
|
|
286
340
|
// Insert default Ollama configuration for new users (no API key needed)
|
|
287
341
|
const defaultOllamaConfig = {
|
|
288
342
|
auth_type: 'ollama',
|
|
@@ -295,13 +349,13 @@ export class FSSLinkDatabase {
|
|
|
295
349
|
last_used: null,
|
|
296
350
|
created_at: new Date().toISOString()
|
|
297
351
|
};
|
|
298
|
-
|
|
352
|
+
// MIGRATED: Insert default Ollama configuration
|
|
353
|
+
safeExec(this.db, `
|
|
299
354
|
INSERT INTO model_configs (
|
|
300
355
|
auth_type, model_name, endpoint_url, api_key, display_name,
|
|
301
356
|
is_favorite, is_active, last_used, created_at
|
|
302
357
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
303
|
-
|
|
304
|
-
insertStmt.run([
|
|
358
|
+
`, [
|
|
305
359
|
defaultOllamaConfig.auth_type,
|
|
306
360
|
defaultOllamaConfig.model_name,
|
|
307
361
|
defaultOllamaConfig.endpoint_url,
|
|
@@ -312,10 +366,8 @@ export class FSSLinkDatabase {
|
|
|
312
366
|
defaultOllamaConfig.last_used,
|
|
313
367
|
defaultOllamaConfig.created_at
|
|
314
368
|
]);
|
|
315
|
-
// Get the inserted Ollama config ID
|
|
316
|
-
const
|
|
317
|
-
const idResult = idStmt.getAsObject({});
|
|
318
|
-
const ollamaId = idResult.id;
|
|
369
|
+
// MIGRATED: Get the inserted Ollama config ID
|
|
370
|
+
const ollamaId = getLastInsertId(this.db);
|
|
319
371
|
// Also insert a default Gemini configuration (inactive) for when users want to configure API keys later
|
|
320
372
|
const defaultGeminiConfig = {
|
|
321
373
|
auth_type: 'gemini-api-key',
|
|
@@ -328,7 +380,13 @@ export class FSSLinkDatabase {
|
|
|
328
380
|
last_used: null,
|
|
329
381
|
created_at: new Date().toISOString()
|
|
330
382
|
};
|
|
331
|
-
|
|
383
|
+
// MIGRATED: Insert default Gemini configuration
|
|
384
|
+
safeExec(this.db, `
|
|
385
|
+
INSERT INTO model_configs (
|
|
386
|
+
auth_type, model_name, endpoint_url, api_key, display_name,
|
|
387
|
+
is_favorite, is_active, last_used, created_at
|
|
388
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
389
|
+
`, [
|
|
332
390
|
defaultGeminiConfig.auth_type,
|
|
333
391
|
defaultGeminiConfig.model_name,
|
|
334
392
|
defaultGeminiConfig.endpoint_url,
|
|
@@ -351,7 +409,13 @@ export class FSSLinkDatabase {
|
|
|
351
409
|
last_used: null,
|
|
352
410
|
created_at: new Date().toISOString()
|
|
353
411
|
};
|
|
354
|
-
|
|
412
|
+
// MIGRATED: Insert default LM Studio configuration
|
|
413
|
+
safeExec(this.db, `
|
|
414
|
+
INSERT INTO model_configs (
|
|
415
|
+
auth_type, model_name, endpoint_url, api_key, display_name,
|
|
416
|
+
is_favorite, is_active, last_used, created_at
|
|
417
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
418
|
+
`, [
|
|
355
419
|
defaultLMStudioConfig.auth_type,
|
|
356
420
|
defaultLMStudioConfig.model_name,
|
|
357
421
|
defaultLMStudioConfig.endpoint_url,
|
|
@@ -374,7 +438,13 @@ export class FSSLinkDatabase {
|
|
|
374
438
|
last_used: null,
|
|
375
439
|
created_at: new Date().toISOString()
|
|
376
440
|
};
|
|
377
|
-
|
|
441
|
+
// MIGRATED: Insert default OpenAI configuration
|
|
442
|
+
safeExec(this.db, `
|
|
443
|
+
INSERT INTO model_configs (
|
|
444
|
+
auth_type, model_name, endpoint_url, api_key, display_name,
|
|
445
|
+
is_favorite, is_active, last_used, created_at
|
|
446
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
447
|
+
`, [
|
|
378
448
|
defaultOpenAIConfig.auth_type,
|
|
379
449
|
defaultOpenAIConfig.model_name,
|
|
380
450
|
defaultOpenAIConfig.endpoint_url,
|
|
@@ -385,30 +455,35 @@ export class FSSLinkDatabase {
|
|
|
385
455
|
defaultOpenAIConfig.last_used,
|
|
386
456
|
defaultOpenAIConfig.created_at
|
|
387
457
|
]);
|
|
388
|
-
// Get the LM Studio ID
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
const lmStudioId = lmStudioResult.id;
|
|
392
|
-
// Insert default provider settings for optimal context windows
|
|
393
|
-
const settingsStmt = this.db.prepare(`
|
|
394
|
-
INSERT INTO provider_settings (provider_id, setting_key, setting_value, updated_at)
|
|
395
|
-
VALUES (?, ?, ?, ?)
|
|
396
|
-
`);
|
|
458
|
+
// MIGRATED: Get the LM Studio ID
|
|
459
|
+
const lmStudioId = getLastInsertId(this.db);
|
|
460
|
+
// MIGRATED: Insert default provider settings for optimal context windows
|
|
397
461
|
const now = new Date().toISOString();
|
|
398
462
|
// Ollama default settings - 32K context window
|
|
399
|
-
|
|
400
|
-
|
|
463
|
+
safeExec(this.db, `
|
|
464
|
+
INSERT INTO provider_settings (provider_id, setting_key, setting_value, updated_at)
|
|
465
|
+
VALUES (?, ?, ?, ?)
|
|
466
|
+
`, [ollamaId, 'num_ctx', '32768', now]);
|
|
467
|
+
safeExec(this.db, `
|
|
468
|
+
INSERT INTO provider_settings (provider_id, setting_key, setting_value, updated_at)
|
|
469
|
+
VALUES (?, ?, ?, ?)
|
|
470
|
+
`, [ollamaId, 'temperature', '0.0', now]);
|
|
401
471
|
// LM Studio default settings - 32K context window
|
|
402
|
-
|
|
403
|
-
|
|
472
|
+
safeExec(this.db, `
|
|
473
|
+
INSERT INTO provider_settings (provider_id, setting_key, setting_value, updated_at)
|
|
474
|
+
VALUES (?, ?, ?, ?)
|
|
475
|
+
`, [lmStudioId, 'num_ctx', '32768', now]);
|
|
476
|
+
safeExec(this.db, `
|
|
477
|
+
INSERT INTO provider_settings (provider_id, setting_key, setting_value, updated_at)
|
|
478
|
+
VALUES (?, ?, ?, ?)
|
|
479
|
+
`, [lmStudioId, 'temperature', '0.0', now]);
|
|
404
480
|
}
|
|
405
|
-
//
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
// No active model found - activate the best available model
|
|
481
|
+
// MIGRATED: ALWAYS ensure there's an active model (even if configs already exist)
|
|
482
|
+
const activeResult = safeQueryFirst(this.db, 'SELECT COUNT(*) as count FROM model_configs WHERE is_active = 1');
|
|
483
|
+
if ((activeResult?.count || 0) === 0) {
|
|
484
|
+
// MIGRATED: No active model found - activate the best available model
|
|
410
485
|
// Priority: Ollama > LM Studio > Gemini > OpenAI
|
|
411
|
-
const
|
|
486
|
+
const bestModel = safeQueryFirst(this.db, `
|
|
412
487
|
SELECT id FROM model_configs
|
|
413
488
|
WHERE auth_type IN ('ollama', 'lmstudio', 'gemini-api-key', 'openai-api-key')
|
|
414
489
|
ORDER BY
|
|
@@ -422,56 +497,51 @@ export class FSSLinkDatabase {
|
|
|
422
497
|
created_at ASC
|
|
423
498
|
LIMIT 1
|
|
424
499
|
`);
|
|
425
|
-
if (
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (settingsCheck.count === 0) {
|
|
434
|
-
const now = new Date().toISOString();
|
|
435
|
-
const addSettingsStmt = this.db.prepare('INSERT INTO provider_settings (provider_id, setting_key, setting_value, updated_at) VALUES (?, ?, ?, ?)');
|
|
436
|
-
addSettingsStmt.run([bestModel.id, 'num_ctx', '32768', now]);
|
|
437
|
-
addSettingsStmt.run([bestModel.id, 'temperature', '0.0', now]);
|
|
438
|
-
}
|
|
500
|
+
if (bestModel) {
|
|
501
|
+
safeExec(this.db, 'UPDATE model_configs SET is_active = 1 WHERE id = ?', [bestModel.id]);
|
|
502
|
+
// MIGRATED: Add default provider settings if they don't exist
|
|
503
|
+
const settingsCheck = safeQueryFirst(this.db, 'SELECT COUNT(*) as count FROM provider_settings WHERE provider_id = ?', [bestModel.id]);
|
|
504
|
+
if ((settingsCheck?.count || 0) === 0) {
|
|
505
|
+
const now = new Date().toISOString();
|
|
506
|
+
safeExec(this.db, 'INSERT INTO provider_settings (provider_id, setting_key, setting_value, updated_at) VALUES (?, ?, ?, ?)', [bestModel.id, 'num_ctx', '32768', now]);
|
|
507
|
+
safeExec(this.db, 'INSERT INTO provider_settings (provider_id, setting_key, setting_value, updated_at) VALUES (?, ?, ?, ?)', [bestModel.id, 'temperature', '0.0', now]);
|
|
439
508
|
}
|
|
440
509
|
}
|
|
441
510
|
}
|
|
442
511
|
}
|
|
443
512
|
/**
|
|
444
513
|
* Get provider settings for a specific provider
|
|
514
|
+
* @todo UPGRADE: Move to ProviderSettingsRepository.findByProviderId()
|
|
445
515
|
*/
|
|
446
516
|
async getProviderSettings(providerId) {
|
|
447
517
|
await this.ensureInitialized();
|
|
448
518
|
if (!this.db)
|
|
449
519
|
return {};
|
|
450
|
-
|
|
520
|
+
// MIGRATED: Using safeQuery wrapper to eliminate manual .free() calls
|
|
521
|
+
const rows = safeQuery(this.db, `
|
|
451
522
|
SELECT setting_key, setting_value
|
|
452
523
|
FROM provider_settings
|
|
453
524
|
WHERE provider_id = ?
|
|
454
|
-
|
|
455
|
-
stmt.bind([providerId]);
|
|
525
|
+
`, [providerId]);
|
|
456
526
|
const settings = {};
|
|
457
|
-
|
|
458
|
-
const row = stmt.getAsObject();
|
|
527
|
+
for (const row of rows) {
|
|
459
528
|
settings[row.setting_key] = row.setting_value;
|
|
460
529
|
}
|
|
461
530
|
return settings;
|
|
462
531
|
}
|
|
463
532
|
/**
|
|
464
533
|
* Save a provider setting
|
|
534
|
+
* @todo UPGRADE: Move to ProviderSettingsRepository.upsert()
|
|
465
535
|
*/
|
|
466
536
|
async saveProviderSetting(providerId, key, value) {
|
|
467
537
|
await this.ensureInitialized();
|
|
468
538
|
if (!this.db)
|
|
469
539
|
return;
|
|
470
|
-
|
|
540
|
+
// MIGRATED: Using safeExec wrapper to eliminate manual .free() calls
|
|
541
|
+
safeExec(this.db, `
|
|
471
542
|
INSERT OR REPLACE INTO provider_settings (provider_id, setting_key, setting_value, updated_at)
|
|
472
543
|
VALUES (?, ?, ?, ?)
|
|
473
|
-
|
|
474
|
-
stmt.run([providerId, key, String(value), new Date().toISOString()]);
|
|
544
|
+
`, [providerId, key, String(value), new Date().toISOString()]);
|
|
475
545
|
}
|
|
476
546
|
/**
|
|
477
547
|
* Get the currently active model configuration
|
|
@@ -509,6 +579,7 @@ export class FSSLinkDatabase {
|
|
|
509
579
|
}
|
|
510
580
|
/**
|
|
511
581
|
* Add or update a model configuration
|
|
582
|
+
* @todo UPGRADE: Move to ModelRepository.upsert()
|
|
512
583
|
*/
|
|
513
584
|
async upsertModelConfig(config) {
|
|
514
585
|
await this.ensureInitialized();
|
|
@@ -516,12 +587,12 @@ export class FSSLinkDatabase {
|
|
|
516
587
|
return -1;
|
|
517
588
|
// Encrypt API key if provided
|
|
518
589
|
const encryptedApiKey = config.apiKey ? this.encryptApiKey(config.apiKey) : null;
|
|
519
|
-
|
|
590
|
+
// MIGRATED: Using safeExec wrapper to eliminate manual .free() calls
|
|
591
|
+
safeExec(this.db, `
|
|
520
592
|
INSERT OR REPLACE INTO model_configs
|
|
521
593
|
(auth_type, model_name, endpoint_url, api_key, display_name, is_favorite, is_active, last_used, source)
|
|
522
594
|
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?)
|
|
523
|
-
|
|
524
|
-
stmt.run([
|
|
595
|
+
`, [
|
|
525
596
|
config.authType,
|
|
526
597
|
config.modelName,
|
|
527
598
|
config.endpointUrl || null,
|
|
@@ -532,9 +603,8 @@ export class FSSLinkDatabase {
|
|
|
532
603
|
config.source || 'db'
|
|
533
604
|
]);
|
|
534
605
|
this.save();
|
|
535
|
-
// Get the last inserted row ID
|
|
536
|
-
|
|
537
|
-
return result[0]?.values[0]?.[0] || -1;
|
|
606
|
+
// MIGRATED: Get the last inserted row ID using helper
|
|
607
|
+
return getLastInsertId(this.db);
|
|
538
608
|
}
|
|
539
609
|
/**
|
|
540
610
|
* Get all model configurations
|
|
@@ -557,29 +627,28 @@ export class FSSLinkDatabase {
|
|
|
557
627
|
*/
|
|
558
628
|
async getFavoriteModels() {
|
|
559
629
|
await this.ensureInitialized();
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
results.push(this.mapRowToModelConfig(stmt.getAsObject()));
|
|
572
|
-
}
|
|
573
|
-
return results;
|
|
630
|
+
return await this.pool.withConnection(async (db) => {
|
|
631
|
+
const query = `
|
|
632
|
+
SELECT id, auth_type, model_name, endpoint_url, api_key, display_name,
|
|
633
|
+
is_favorite, is_active, last_used, created_at, source
|
|
634
|
+
FROM model_configs
|
|
635
|
+
WHERE is_favorite = 1
|
|
636
|
+
ORDER BY last_used DESC, created_at DESC
|
|
637
|
+
`;
|
|
638
|
+
const results = await this.queryOptimizer.executeQuery(db, query);
|
|
639
|
+
return results.map(row => this.mapRowToModelConfig(row));
|
|
640
|
+
});
|
|
574
641
|
}
|
|
575
642
|
/**
|
|
576
643
|
* Get recently used models
|
|
644
|
+
* @todo UPGRADE: Move to ModelRepository.findRecentlyUsed()
|
|
577
645
|
*/
|
|
578
646
|
async getRecentModels(limit = 10) {
|
|
579
647
|
await this.ensureInitialized();
|
|
580
648
|
if (!this.db)
|
|
581
649
|
return [];
|
|
582
|
-
|
|
650
|
+
// MIGRATED: Using safeQuery wrapper to eliminate manual .free() calls
|
|
651
|
+
const rows = safeQuery(this.db, `
|
|
583
652
|
SELECT id, auth_type, model_name, endpoint_url, api_key, display_name,
|
|
584
653
|
is_favorite, is_active, last_used, created_at, source
|
|
585
654
|
FROM model_configs
|
|
@@ -587,11 +656,7 @@ export class FSSLinkDatabase {
|
|
|
587
656
|
ORDER BY last_used DESC
|
|
588
657
|
LIMIT ?
|
|
589
658
|
`, [limit]);
|
|
590
|
-
|
|
591
|
-
while (stmt.step()) {
|
|
592
|
-
results.push(this.mapRowToModelConfig(stmt.getAsObject()));
|
|
593
|
-
}
|
|
594
|
-
return results;
|
|
659
|
+
return rows.map(row => this.mapRowToModelConfig(row));
|
|
595
660
|
}
|
|
596
661
|
/**
|
|
597
662
|
* Toggle favorite status for a model
|
|
@@ -631,30 +696,29 @@ export class FSSLinkDatabase {
|
|
|
631
696
|
this.save();
|
|
632
697
|
}
|
|
633
698
|
/**
|
|
634
|
-
* Get user preference
|
|
699
|
+
* Get user preference by key
|
|
700
|
+
* @todo UPGRADE: Move to PreferencesRepository.findByKey()
|
|
635
701
|
*/
|
|
636
702
|
async getUserPreference(key) {
|
|
637
703
|
await this.ensureInitialized();
|
|
638
704
|
if (!this.db)
|
|
639
705
|
return null;
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
return result.value || null;
|
|
644
|
-
}
|
|
645
|
-
return null;
|
|
706
|
+
// MIGRATED: Using safeQueryFirst wrapper to eliminate manual .free() calls
|
|
707
|
+
const result = safeQueryFirst(this.db, 'SELECT value FROM user_preferences WHERE key = ?', [key]);
|
|
708
|
+
return result?.value || null;
|
|
646
709
|
}
|
|
647
710
|
/**
|
|
648
711
|
* Get all user preferences
|
|
712
|
+
* @todo UPGRADE: Move to PreferencesRepository.findAll()
|
|
649
713
|
*/
|
|
650
714
|
async getAllUserPreferences() {
|
|
651
715
|
await this.ensureInitialized();
|
|
652
716
|
if (!this.db)
|
|
653
717
|
return {};
|
|
654
|
-
|
|
718
|
+
// MIGRATED: Using safeQuery wrapper to eliminate manual .free() calls
|
|
719
|
+
const rows = safeQuery(this.db, 'SELECT key, value FROM user_preferences');
|
|
655
720
|
const prefs = {};
|
|
656
|
-
|
|
657
|
-
const row = stmt.getAsObject();
|
|
721
|
+
for (const row of rows) {
|
|
658
722
|
prefs[row.key] = row.value;
|
|
659
723
|
}
|
|
660
724
|
return prefs;
|
|
@@ -811,26 +875,72 @@ export class FSSLinkDatabase {
|
|
|
811
875
|
* Record provider usage for intelligent default selection
|
|
812
876
|
*/
|
|
813
877
|
async recordProviderUsage(providerId, tokensUsed, sessionDuration) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
878
|
+
console.log(`recordProviderUsage called: providerId=${providerId}, tokensUsed=${tokensUsed}, sessionDuration=${sessionDuration}`);
|
|
879
|
+
try {
|
|
880
|
+
// Ensure database is fully initialized including migrations
|
|
881
|
+
debugLog('Ensuring database is initialized for usage recording...');
|
|
882
|
+
await this.ensureInitialized();
|
|
883
|
+
// Triple-check initialization completed with detailed logging
|
|
884
|
+
if (!this.initialized) {
|
|
885
|
+
console.warn('Database not initialized after ensureInitialized(), skipping usage recording');
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
debugLog('Database initialized, attempting to record usage...');
|
|
889
|
+
await this.pool.withConnection(async (db) => {
|
|
890
|
+
// Check if table exists before trying to use it
|
|
891
|
+
debugLog('Checking if provider_usage_stats table exists...');
|
|
892
|
+
const tableCheck = db.exec(`
|
|
893
|
+
SELECT name FROM sqlite_master
|
|
894
|
+
WHERE type='table' AND name='provider_usage_stats'
|
|
895
|
+
`);
|
|
896
|
+
if (tableCheck.length === 0) {
|
|
897
|
+
console.error('provider_usage_stats table does not exist, this should not happen after initialization!');
|
|
898
|
+
// Emergency table creation as absolute last resort
|
|
899
|
+
console.log('Attempting emergency table creation...');
|
|
900
|
+
try {
|
|
901
|
+
db.exec(`
|
|
902
|
+
CREATE TABLE IF NOT EXISTS provider_usage_stats (
|
|
903
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
904
|
+
provider_id INTEGER NOT NULL,
|
|
905
|
+
tokens_used INTEGER DEFAULT 0,
|
|
906
|
+
session_duration_seconds INTEGER DEFAULT 0,
|
|
907
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
908
|
+
)
|
|
909
|
+
`);
|
|
910
|
+
console.log('Emergency table creation completed');
|
|
911
|
+
}
|
|
912
|
+
catch (emergencyError) {
|
|
913
|
+
console.error('Emergency table creation failed:', emergencyError);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
debugLog('provider_usage_stats table exists, proceeding with insert');
|
|
919
|
+
}
|
|
920
|
+
// MIGRATED: Insert usage statistics and update timestamp in transaction
|
|
921
|
+
console.log('Inserting usage statistics and updating timestamp...');
|
|
922
|
+
safeTransaction(db, () => {
|
|
923
|
+
// Insert usage statistics
|
|
924
|
+
safeExec(db, `
|
|
925
|
+
INSERT INTO provider_usage_stats (provider_id, tokens_used, session_duration_seconds, created_at)
|
|
926
|
+
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
|
927
|
+
`, [providerId, tokensUsed, sessionDuration]);
|
|
928
|
+
// Update last_used timestamp in model_configs
|
|
929
|
+
safeExec(db, `
|
|
930
|
+
UPDATE model_configs
|
|
931
|
+
SET last_used = CURRENT_TIMESTAMP
|
|
932
|
+
WHERE id = ?
|
|
933
|
+
`, [providerId]);
|
|
934
|
+
});
|
|
935
|
+
console.log('Usage statistics and timestamp updated successfully');
|
|
936
|
+
});
|
|
937
|
+
this.scheduleBatchedSave();
|
|
938
|
+
console.log('Provider usage recording completed successfully');
|
|
939
|
+
}
|
|
940
|
+
catch (error) {
|
|
941
|
+
console.error('Failed to record provider usage (detailed error):', error);
|
|
942
|
+
console.error('Error stack:', error instanceof Error ? error.stack : 'No stack trace');
|
|
943
|
+
}
|
|
834
944
|
}
|
|
835
945
|
/**
|
|
836
946
|
* Save custom endpoint for a provider
|
|
@@ -857,24 +967,19 @@ export class FSSLinkDatabase {
|
|
|
857
967
|
}
|
|
858
968
|
/**
|
|
859
969
|
* Get custom endpoints for a provider
|
|
970
|
+
* @todo UPGRADE: Move to CustomEndpointsRepository.findByProviderId()
|
|
860
971
|
*/
|
|
861
972
|
async getCustomEndpoints(providerId) {
|
|
862
973
|
await this.ensureInitialized();
|
|
863
974
|
if (!this.db)
|
|
864
975
|
return [];
|
|
865
|
-
|
|
976
|
+
// MIGRATED: Using safeQuery wrapper to eliminate manual .free() calls
|
|
977
|
+
return safeQuery(this.db, `
|
|
866
978
|
SELECT id, provider_id, name, url, description, is_default, created_at
|
|
867
979
|
FROM custom_endpoints
|
|
868
980
|
WHERE provider_id = ?
|
|
869
981
|
ORDER BY is_default DESC, created_at DESC
|
|
870
|
-
|
|
871
|
-
stmt.bind([providerId]);
|
|
872
|
-
const endpoints = [];
|
|
873
|
-
while (stmt.step()) {
|
|
874
|
-
const row = stmt.getAsObject();
|
|
875
|
-
endpoints.push(row);
|
|
876
|
-
}
|
|
877
|
-
return endpoints;
|
|
982
|
+
`, [providerId]);
|
|
878
983
|
}
|
|
879
984
|
/**
|
|
880
985
|
* Get smart default provider with settings
|
|
@@ -888,16 +993,17 @@ export class FSSLinkDatabase {
|
|
|
888
993
|
}
|
|
889
994
|
/**
|
|
890
995
|
* Get smart default provider based on usage and preferences
|
|
996
|
+
* @todo UPGRADE: Move to ModelRepository.findSmartDefault()
|
|
891
997
|
*/
|
|
892
998
|
async getSmartDefaultProvider() {
|
|
893
999
|
await this.ensureInitialized();
|
|
894
1000
|
if (!this.db)
|
|
895
1001
|
return null;
|
|
896
|
-
// Priority order
|
|
1002
|
+
// MIGRATED: Priority order using safeQueryFirst wrapper
|
|
897
1003
|
// 1. User-marked favorite (is_favorite = 1)
|
|
898
1004
|
// 2. Most used provider in last 30 days
|
|
899
1005
|
// 3. Most recently used provider
|
|
900
|
-
const
|
|
1006
|
+
const result = safeQueryFirst(this.db, `
|
|
901
1007
|
SELECT
|
|
902
1008
|
m.id, m.auth_type, m.model_name, m.endpoint_url, m.api_key,
|
|
903
1009
|
m.display_name, m.is_favorite, m.is_active, m.last_used, m.created_at,
|
|
@@ -918,9 +1024,8 @@ export class FSSLinkDatabase {
|
|
|
918
1024
|
m.created_at DESC
|
|
919
1025
|
LIMIT 1
|
|
920
1026
|
`);
|
|
921
|
-
if (!
|
|
1027
|
+
if (!result)
|
|
922
1028
|
return null;
|
|
923
|
-
const result = stmt.getAsObject();
|
|
924
1029
|
return {
|
|
925
1030
|
id: result.id,
|
|
926
1031
|
authType: result.auth_type,
|
|
@@ -934,18 +1039,18 @@ export class FSSLinkDatabase {
|
|
|
934
1039
|
}
|
|
935
1040
|
/**
|
|
936
1041
|
* Get a specific model configuration by ID
|
|
1042
|
+
* @todo UPGRADE: Move to ModelRepository.findById()
|
|
937
1043
|
*/
|
|
938
1044
|
async getModelById(modelId) {
|
|
939
1045
|
await this.ensureInitialized();
|
|
940
1046
|
if (!this.db)
|
|
941
1047
|
return null;
|
|
942
|
-
|
|
1048
|
+
// MIGRATED: Using safeQueryFirst wrapper to eliminate manual .free() calls
|
|
1049
|
+
const result = safeQueryFirst(this.db, `
|
|
943
1050
|
SELECT * FROM model_configs WHERE id = ?
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
if (!stmt.step())
|
|
1051
|
+
`, [modelId]);
|
|
1052
|
+
if (!result)
|
|
947
1053
|
return null;
|
|
948
|
-
const result = stmt.getAsObject();
|
|
949
1054
|
return {
|
|
950
1055
|
id: result.id,
|
|
951
1056
|
authType: result.auth_type,
|
|
@@ -957,7 +1062,238 @@ export class FSSLinkDatabase {
|
|
|
957
1062
|
isActive: Boolean(result.is_active)
|
|
958
1063
|
};
|
|
959
1064
|
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Get query performance metrics from the optimizer
|
|
1067
|
+
*/
|
|
1068
|
+
async getQueryMetrics() {
|
|
1069
|
+
return this.queryOptimizer.getQueryMetrics();
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Get slow queries (above threshold)
|
|
1073
|
+
*/
|
|
1074
|
+
async getSlowQueries(thresholdMs = 100) {
|
|
1075
|
+
return this.queryOptimizer.getSlowQueries(thresholdMs);
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Get most frequently executed queries
|
|
1079
|
+
*/
|
|
1080
|
+
async getFrequentQueries(limit = 10) {
|
|
1081
|
+
return this.queryOptimizer.getFrequentQueries(limit);
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Get prepared statement cache statistics
|
|
1085
|
+
*/
|
|
1086
|
+
async getCacheStats() {
|
|
1087
|
+
return this.queryOptimizer.getCacheStats();
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Clear query performance metrics
|
|
1091
|
+
*/
|
|
1092
|
+
async clearQueryMetrics() {
|
|
1093
|
+
this.queryOptimizer.clearMetrics();
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Optimize database by running ANALYZE
|
|
1097
|
+
*/
|
|
1098
|
+
async optimizeDatabase() {
|
|
1099
|
+
await this.pool.withConnection(async (db) => {
|
|
1100
|
+
await this.queryOptimizer.optimizeDatabase(db);
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Get comprehensive database statistics
|
|
1105
|
+
*/
|
|
1106
|
+
async getDatabaseStats() {
|
|
1107
|
+
return await this.pool.withConnection(async (db) => {
|
|
1108
|
+
return await this.queryOptimizer.getDatabaseStats(db);
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Run VACUUM to reclaim space and defragment
|
|
1113
|
+
*/
|
|
1114
|
+
async vacuumDatabase() {
|
|
1115
|
+
await this.pool.withConnection(async (db) => {
|
|
1116
|
+
await this.queryOptimizer.vacuumDatabase(db);
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Collect comprehensive database metrics
|
|
1121
|
+
*/
|
|
1122
|
+
async collectMetrics() {
|
|
1123
|
+
return await this.pool.withConnection(async (db) => {
|
|
1124
|
+
return await this.metricsCollector.collectMetrics(db, this.queryOptimizer, this.pool, this.backupManager);
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Get historical metrics for trend analysis
|
|
1129
|
+
*/
|
|
1130
|
+
async getHistoricalMetrics(hours = 24) {
|
|
1131
|
+
return this.metricsCollector.getHistoricalMetrics(hours);
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Get performance trends
|
|
1135
|
+
*/
|
|
1136
|
+
async getPerformanceTrends(hours = 24) {
|
|
1137
|
+
return this.metricsCollector.getPerformanceTrends(hours);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Generate a comprehensive database health report
|
|
1141
|
+
*/
|
|
1142
|
+
async generateHealthReport() {
|
|
1143
|
+
const metrics = await this.collectMetrics();
|
|
1144
|
+
const trends = await this.getPerformanceTrends();
|
|
1145
|
+
const recommendations = [];
|
|
1146
|
+
// Generate recommendations based on metrics
|
|
1147
|
+
if (metrics.queryPerformance.averageQueryTime > 100) {
|
|
1148
|
+
recommendations.push('Consider running ANALYZE to optimize query plans');
|
|
1149
|
+
}
|
|
1150
|
+
if (metrics.cachePerformance.hitRate < 80) {
|
|
1151
|
+
recommendations.push('Increase prepared statement cache size for better performance');
|
|
1152
|
+
}
|
|
1153
|
+
if (metrics.connectionPool.connectionUtilization > 90) {
|
|
1154
|
+
recommendations.push('Consider increasing database connection pool size');
|
|
1155
|
+
}
|
|
1156
|
+
if (metrics.database.freelistCount > 1000) {
|
|
1157
|
+
recommendations.push('Run VACUUM to reclaim unused database space');
|
|
1158
|
+
}
|
|
1159
|
+
if (metrics.usage.lastBackupAge > 24) {
|
|
1160
|
+
recommendations.push('Create a fresh backup - last backup is over 24 hours old');
|
|
1161
|
+
}
|
|
1162
|
+
if (metrics.health.overallScore < 70) {
|
|
1163
|
+
recommendations.push('Database health is below optimal - consider maintenance');
|
|
1164
|
+
}
|
|
1165
|
+
return {
|
|
1166
|
+
metrics,
|
|
1167
|
+
trends,
|
|
1168
|
+
recommendations
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Set custom metrics thresholds
|
|
1173
|
+
*/
|
|
1174
|
+
async setMetricsThresholds(thresholds) {
|
|
1175
|
+
this.metricsCollector.setThresholds(thresholds);
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Clear metrics history (for testing or cleanup)
|
|
1179
|
+
*/
|
|
1180
|
+
async clearMetricsHistory() {
|
|
1181
|
+
this.metricsCollector.clearHistory();
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Perform comprehensive health check
|
|
1185
|
+
*/
|
|
1186
|
+
async performHealthCheck() {
|
|
1187
|
+
const metrics = await this.collectMetrics();
|
|
1188
|
+
return await this.healthMonitor.checkHealth(metrics);
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Get maintenance recommendations
|
|
1192
|
+
*/
|
|
1193
|
+
async getMaintenanceRecommendations() {
|
|
1194
|
+
const metrics = await this.collectMetrics();
|
|
1195
|
+
return this.healthMonitor.getMaintenanceRecommendations(metrics);
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Get recent health check history
|
|
1199
|
+
*/
|
|
1200
|
+
async getHealthHistory(hours = 24) {
|
|
1201
|
+
return this.healthMonitor.getRecentHealthChecks(hours);
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Update health monitoring thresholds
|
|
1205
|
+
*/
|
|
1206
|
+
async updateHealthThresholds(thresholds) {
|
|
1207
|
+
this.healthMonitor.updateThresholds(thresholds);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Clear health monitoring history
|
|
1211
|
+
*/
|
|
1212
|
+
async clearHealthHistory() {
|
|
1213
|
+
this.healthMonitor.clearHistory();
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Validate database schema against expected structure
|
|
1217
|
+
*/
|
|
1218
|
+
async validateSchema() {
|
|
1219
|
+
return await this.pool.withConnection(async (db) => {
|
|
1220
|
+
return await this.schemaValidator.validateSchema(db);
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Validate specific table schema
|
|
1225
|
+
*/
|
|
1226
|
+
async validateTableSchema(tableName) {
|
|
1227
|
+
return await this.pool.withConnection(async (db) => {
|
|
1228
|
+
return await this.schemaValidator.validateTableSchema(db, tableName);
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Check if database needs migration based on schema validation
|
|
1233
|
+
*/
|
|
1234
|
+
async needsSchemaMigration() {
|
|
1235
|
+
return await this.pool.withConnection(async (db) => {
|
|
1236
|
+
return await this.schemaValidator.needsMigration(db);
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Get missing tables from schema
|
|
1241
|
+
*/
|
|
1242
|
+
async getMissingTables() {
|
|
1243
|
+
return await this.pool.withConnection(async (db) => {
|
|
1244
|
+
return await this.schemaValidator.getMissingTables(db);
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Get missing indexes from schema
|
|
1249
|
+
*/
|
|
1250
|
+
async getMissingIndexes() {
|
|
1251
|
+
return await this.pool.withConnection(async (db) => {
|
|
1252
|
+
return await this.schemaValidator.getMissingIndexes(db);
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Get formatted schema validation report
|
|
1257
|
+
*/
|
|
1258
|
+
async getSchemaValidationReport() {
|
|
1259
|
+
const result = await this.validateSchema();
|
|
1260
|
+
return this.schemaValidator.formatValidationReport(result);
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Perform comprehensive database integrity check
|
|
1264
|
+
*/
|
|
1265
|
+
async performIntegrityCheck() {
|
|
1266
|
+
// Run all checks in parallel
|
|
1267
|
+
const [schemaValidation, healthCheck, recommendations] = await Promise.all([
|
|
1268
|
+
this.validateSchema(),
|
|
1269
|
+
this.performHealthCheck(),
|
|
1270
|
+
this.getMaintenanceRecommendations()
|
|
1271
|
+
]);
|
|
1272
|
+
// Determine overall status
|
|
1273
|
+
let overallStatus = 'healthy';
|
|
1274
|
+
if (!schemaValidation.isValid ||
|
|
1275
|
+
healthCheck.some(h => h.status === 'critical' || h.status === 'error')) {
|
|
1276
|
+
overallStatus = 'critical';
|
|
1277
|
+
}
|
|
1278
|
+
else if (schemaValidation.warnings.length > 0 ||
|
|
1279
|
+
healthCheck.some(h => h.status === 'warning') ||
|
|
1280
|
+
recommendations.length > 0) {
|
|
1281
|
+
overallStatus = 'warning';
|
|
1282
|
+
}
|
|
1283
|
+
return {
|
|
1284
|
+
schemaValidation,
|
|
1285
|
+
healthCheck,
|
|
1286
|
+
recommendations,
|
|
1287
|
+
overallStatus
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
960
1290
|
}
|
|
1291
|
+
// Debug logging helper - only log if DEBUG environment variable is set
|
|
1292
|
+
const debugLog = (message) => {
|
|
1293
|
+
if (process.env['DEBUG'] === '1' || process.env['DEBUG'] === 'true') {
|
|
1294
|
+
console.log(`[DB] ${message}`);
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
961
1297
|
// Singleton instance for global access
|
|
962
1298
|
let databaseInstance = null;
|
|
963
1299
|
/**
|
|
@@ -965,8 +1301,11 @@ let databaseInstance = null;
|
|
|
965
1301
|
*/
|
|
966
1302
|
export async function getFSSLinkDatabase() {
|
|
967
1303
|
if (!databaseInstance) {
|
|
1304
|
+
debugLog('Creating new FSS Link database instance...');
|
|
968
1305
|
databaseInstance = new FSSLinkDatabase();
|
|
1306
|
+
debugLog('Initializing FSS Link database...');
|
|
969
1307
|
await databaseInstance.initialize();
|
|
1308
|
+
debugLog('FSS Link database ready for use');
|
|
970
1309
|
}
|
|
971
1310
|
return databaseInstance;
|
|
972
1311
|
}
|