mem100x
Version:
⚡ The FASTEST MCP memory server ever built - 66k+ entities/sec with intelligent context detection
463 lines • 18.7 kB
JavaScript
"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