UNPKG

mem100x

Version:

⚡ The FASTEST MCP memory server ever built - 66k+ entities/sec with intelligent context detection

463 lines 18.7 kB
"use strict"; /** * Multi-Database Manager with Synchronous Operations * Intelligently manages multiple memory contexts with automatic routing */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MultiDatabaseManager = void 0; const database_js_1 = require("./database.js"); const context_confidence_js_1 = require("./context-confidence.js"); const fs_1 = require("fs"); const path_1 = require("path"); const logger_js_1 = require("./utils/logger.js"); class MultiDatabaseManager { databases = new Map(); _currentContext; entityContextMap = new Map(); confidenceScorer; config; get currentContext() { return this._currentContext; } constructor(config) { this._currentContext = config.multiContext.defaultContext; this.initialize(config); } initialize(config) { this.config = this.loadConfig(config); this.initializeDatabases(); this.loadEntityMappings(); // Convert database configs to the format expected by ContextConfidenceScorer const scorerConfigs = {}; for (const [context, dbConfig] of Object.entries(this.config.databases)) { scorerConfigs[context] = { enabled: true, dbPath: dbConfig.path, patterns: dbConfig.patterns, entityTypes: dbConfig.entityTypes, autoDetect: this.config.autoDetect }; } this.confidenceScorer = new context_confidence_js_1.ContextConfidenceScorer(scorerConfigs, this.entityContextMap); } loadConfig(config) { return { databases: { personal: { path: config.multiContext.personalDbPath, patterns: ['personal', 'family', 'health', 'hobby'], entityTypes: ['person', 'family_member', 'friend'] }, work: { path: config.multiContext.workDbPath, patterns: ['work', 'project', 'colleague', 'meeting'], entityTypes: ['project', 'company', 'colleague'] } }, defaultContext: config.multiContext.defaultContext, autoDetect: true, detectionSettings: { entityWeight: 0.4, typeWeight: 0.25, patternWeight: 0.15, relationWeight: 0.1, existingEntityWeight: 0.1 } }; } initializeDatabases() { for (const [context, dbConfig] of Object.entries(this.config.databases)) { const db = new database_js_1.MemoryDatabase(dbConfig.path); this.databases.set(context, db); } } loadEntityMappings() { this.entityContextMap.clear(); for (const [context, db] of this.databases) { const graph = db.readGraph(); for (const entity of graph.entities) { this.entityContextMap.set(entity.name.toLowerCase(), context); } } } detectContext(data) { if (!this.config.autoDetect) { return { context: this._currentContext, confidence: 1.0 }; } if (data.context && this.databases.has(data.context)) { return { context: data.context, confidence: 1.0 }; } const scores = this.confidenceScorer.scoreContexts(data, Array.from(this.databases.keys())); if (scores.length === 0) { return { context: this._currentContext, confidence: 0.0 }; } return { context: scores[0].context, confidence: scores[0].confidence }; } getContextInfo() { const contexts = {}; for (const [context, db] of this.databases.entries()) { const stats = db.getStats(); contexts[context] = { path: db.dbPath, entities: stats.totalEntities, relations: stats.totalRelations, sizeKb: stats.databaseSizeKb, }; } return { currentContext: this.currentContext, contexts, lastDetection: this.confidenceScorer.lastDetection || null, }; } setContext(context) { if (this.databases.has(context)) { this._currentContext = context; return `Switched to ${context} context`; } throw new Error(`Unknown context: ${context}.`); } createEntities(entities, context) { const targetContext = (context ? { context, confidence: 1.0 } : this.detectContext({ entities })).context; const db = this.databases.get(targetContext); if (!db) throw new Error(`Invalid context: ${targetContext}`); const created = db.createEntities(entities); for (const entity of created) { this.entityContextMap.set(entity.name.toLowerCase(), targetContext); } return created.map(e => ({ ...e, _context: targetContext })); } createRelations(relations, context) { const detection = context ? { context, confidence: 1.0 } : this.detectContext({ relations }); const targetContext = detection.context; const db = this.databases.get(targetContext); if (!db) throw new Error(`Invalid context: ${targetContext}`); const created = db.createRelations(relations); return created.map(r => ({ ...r, _context: targetContext })); } searchNodes(options) { if (options.context) { const db = this.databases.get(options.context); if (!db) throw new Error(`Invalid context: ${options.context}`); const results = db.searchNodes(options); return { entities: results.entities.map(e => ({ ...e, _context: options.context })), relations: results.relations.map(r => ({ ...r, _context: options.context })) }; } // Search across all contexts const allEntities = []; const allRelations = []; for (const [context, db] of this.databases) { const results = db.searchNodes(options); allEntities.push(...results.entities.map(e => ({ ...e, _context: context }))); allRelations.push(...results.relations.map(r => ({ ...r, _context: context }))); } return { entities: allEntities, relations: allRelations }; } readGraph(limit, offset = 0, context) { if (context) { const db = this.databases.get(context); if (!db) throw new Error(`Invalid context: ${context}`); const graph = db.readGraph(limit, offset); return { entities: graph.entities.map(e => ({ ...e, _context: context })), relations: graph.relations.map(r => ({ ...r, _context: context })), pagination: graph.pagination }; } // Read from current context const db = this.databases.get(this._currentContext); if (!db) throw new Error(`Invalid current context: ${this._currentContext}`); const graph = db.readGraph(limit, offset); return { entities: graph.entities.map(e => ({ ...e, _context: this._currentContext })), relations: graph.relations.map(r => ({ ...r, _context: this._currentContext })), pagination: graph.pagination }; } openNodes(names, context) { if (context) { const db = this.databases.get(context); if (!db) throw new Error(`Invalid context: ${context}`); const results = db.openNodes(names); return { entities: results.entities.map(e => ({ ...e, _context: context })), relations: results.relations.map(r => ({ ...r, _context: context })) }; } // Search all contexts for requested nodes const allEntities = []; const entityNamesFound = new Set(); for (const [ctx, db] of this.databases) { const remaining = names.filter(name => !entityNamesFound.has(name.toLowerCase())); if (remaining.length === 0) break; const results = db.openNodes(remaining); for (const entity of results.entities) { allEntities.push({ ...entity, _context: ctx }); entityNamesFound.add(entity.name.toLowerCase()); } } // Get relations for found entities const foundNames = allEntities.map(e => e.name); const allRelations = []; if (foundNames.length > 0) { for (const [ctx, db] of this.databases) { const relations = db.getRelationsForEntities(foundNames); allRelations.push(...relations.map(r => ({ ...r, _context: ctx }))); } } return { entities: allEntities, relations: allRelations }; } addObservations(updates, context) { if (context) { const db = this.databases.get(context); if (!db) throw new Error(`Invalid context: ${context}`); db.addObservations(updates); return; } // Group updates by entity context const updatesByContext = new Map(); for (const update of updates) { const entityContext = this.entityContextMap.get(update.entityName.toLowerCase()) || this._currentContext; if (!updatesByContext.has(entityContext)) { updatesByContext.set(entityContext, []); } updatesByContext.get(entityContext).push(update); } // Apply updates to respective databases for (const [ctx, contextUpdates] of updatesByContext) { const db = this.databases.get(ctx); if (db) { db.addObservations(contextUpdates); } } } deleteObservations(deletions, context) { if (context) { const db = this.databases.get(context); if (!db) throw new Error(`Invalid context: ${context}`); db.deleteObservations(deletions); return; } // Group deletions by entity context const deletionsByContext = new Map(); for (const deletion of deletions) { const entityContext = this.entityContextMap.get(deletion.entityName.toLowerCase()) || this._currentContext; if (!deletionsByContext.has(entityContext)) { deletionsByContext.set(entityContext, []); } deletionsByContext.get(entityContext).push(deletion); } // Apply deletions to respective databases for (const [ctx, contextDeletions] of deletionsByContext) { const db = this.databases.get(ctx); if (db) { db.deleteObservations(contextDeletions); } } } deleteEntities(entityNames, context) { if (context) { const db = this.databases.get(context); if (!db) throw new Error(`Invalid context: ${context}`); db.deleteEntities(entityNames); for (const name of entityNames) { this.entityContextMap.delete(name.toLowerCase()); } return; } // Delete from appropriate contexts const entitiesByContext = new Map(); for (const name of entityNames) { const entityContext = this.entityContextMap.get(name.toLowerCase()) || this._currentContext; if (!entitiesByContext.has(entityContext)) { entitiesByContext.set(entityContext, []); } entitiesByContext.get(entityContext).push(name); } for (const [ctx, names] of entitiesByContext) { const db = this.databases.get(ctx); if (db) { db.deleteEntities(names); for (const name of names) { this.entityContextMap.delete(name.toLowerCase()); } } } } deleteRelations(relations, context) { const targetContext = context || this.detectContext({ relations }).context; const db = this.databases.get(targetContext); if (!db) throw new Error(`Invalid context: ${targetContext}`); db.deleteRelations(relations); } getStats(context) { if (context) { const db = this.databases.get(context); if (!db) throw new Error(`Invalid context: ${context}`); return db.getStats(); } // Aggregate stats from all contexts const allStats = { totalEntities: 0, totalRelations: 0, contexts: {} }; for (const [ctx, db] of this.databases) { const stats = db.getStats(); allStats.totalEntities += stats.totalEntities; allStats.totalRelations += stats.totalRelations; allStats.contexts[ctx] = stats; } return allStats; } beginTransaction(name) { // Transactions are context-specific const transactionId = `txn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // In a real implementation, you'd track active transactions return transactionId; } commitTransaction() { // Commit any active transactions // In this implementation, transactions are handled at the database level } rollbackTransaction() { // Rollback any active transactions // In this implementation, transactions are handled at the database level } createBackup(backupPath, context) { const targetContext = context || this._currentContext; const db = this.databases.get(targetContext); if (!db) throw new Error(`Invalid context: ${targetContext}`); const actualBackupPath = backupPath || `./backups/backup-${targetContext}-${Date.now()}.db`; const dir = (0, path_1.dirname)(actualBackupPath); if (!(0, fs_1.existsSync)(dir)) { (0, fs_1.mkdirSync)(dir, { recursive: true }); } db.backup(actualBackupPath); return { path: actualBackupPath, size: (0, fs_1.statSync)(actualBackupPath).size, context: targetContext, timestamp: new Date().toISOString() }; } restoreBackup(backupPath, context) { const targetContext = context || this._currentContext; if (!(0, fs_1.existsSync)(backupPath)) { throw new Error('Backup file not found'); } // Check if there's an active transaction if (this.isInTransaction && this.isInTransaction()) { this.rollbackTransaction(); } const dbConfig = this.config.databases[targetContext]; if (!dbConfig) throw new Error(`Invalid context: ${targetContext}`); // Create safety backup before restore const safetyBackupPath = `${dbConfig.path}.safety-${Date.now()}`; const db = this.databases.get(targetContext); if (db && (0, fs_1.existsSync)(dbConfig.path)) { try { (0, fs_1.copyFileSync)(dbConfig.path, safetyBackupPath); (0, logger_js_1.logInfo)('Safety backup created', { path: safetyBackupPath }); } catch (error) { // Continue with restore even if safety backup fails (0, logger_js_1.logInfo)('Failed to create safety backup', { error }); } } // Close existing database if (db) { db.close(); } // Copy backup to database path (0, fs_1.copyFileSync)(backupPath, dbConfig.path); // Also restore bloom filter if it exists const backupBloomPath = backupPath.replace('.db', '.cbloom'); const targetBloomPath = dbConfig.path.replace('.db', '.cbloom'); if ((0, fs_1.existsSync)(backupBloomPath)) { (0, fs_1.copyFileSync)(backupBloomPath, targetBloomPath); } // Reinitialize database const newDb = new database_js_1.MemoryDatabase(dbConfig.path); this.databases.set(targetContext, newDb); // Reload entity mappings this.loadEntityMappings(); const stats = newDb.getStats(); return { backupPath, context: targetContext, stats: { entityCount: stats.totalEntities, relationCount: stats.totalRelations } }; } isInTransaction() { // Placeholder for transaction checking return false; } getNeighbors(entityName, options) { const targetContext = options.context || this.entityContextMap.get(entityName.toLowerCase()) || this._currentContext; const db = this.databases.get(targetContext); if (!db) throw new Error(`Invalid context: ${targetContext}`); // For now, return empty result as this would require implementing graph traversal return { entities: [], relations: [] }; } findShortestPath(from, to, options) { const fromContext = this.entityContextMap.get(from.toLowerCase()); const toContext = this.entityContextMap.get(to.toLowerCase()); if (fromContext !== toContext) { return { found: false, path: [], distance: -1 }; } const targetContext = options.context || fromContext || this._currentContext; const db = this.databases.get(targetContext); if (!db) throw new Error(`Invalid context: ${targetContext}`); // For now, return not found as this would require implementing graph algorithms return { found: false, path: [], distance: -1 }; } getEntity(name, context) { if (context) { const db = this.databases.get(context); if (!db) throw new Error(`Invalid context: ${context}`); const entity = db.getEntity(name); return entity || null; } // Search all contexts for (const [ctx, db] of this.databases) { const entity = db.getEntity(name); if (entity) { return { ...entity, _context: ctx }; } } return null; } close() { this.closeAll(); } closeAll() { for (const db of this.databases.values()) { db.close(); } } } exports.MultiDatabaseManager = MultiDatabaseManager; //# sourceMappingURL=multi-database.js.map