memory-engineering
Version:
Advanced Memory-Aware Code Context System with claude-flow-inspired architecture, showcasing MongoDB + Voyage AI strategic positioning
194 lines (159 loc) • 5.8 kB
text/typescript
/**
* MongoDB Backend Implementation
* Following claude-flow-main proven patterns for robust memory storage
*/
import { MongoClient, Db, Collection } from 'mongodb';
import { IMemoryBackend, MemoryEntry, MemoryQuery, MemoryBackendConfig, MemoryBackendError } from './backend-interface.js';
export class MongoDBBackend implements IMemoryBackend {
private client?: MongoClient;
private db?: Db;
private collection?: Collection<MemoryEntry>;
private initialized = false;
constructor(private config: MemoryBackendConfig) {}
async initialize(): Promise<void> {
if (this.initialized) return;
try {
this.client = new MongoClient(this.config.connectionString);
await this.client.connect();
const dbName = this.config.databaseName || 'memory_engineering';
this.db = this.client.db(dbName);
this.collection = this.db.collection(this.config.collectionName || 'memories');
if (this.config.enableIndexes !== false) {
await this.createIndexes();
}
this.initialized = true;
} catch (error) {
throw new MemoryBackendError('Failed to initialize MongoDB backend', { error });
}
}
async shutdown(): Promise<void> {
if (this.client) {
await this.client.close();
delete this.client;
delete this.db;
delete this.collection;
}
this.initialized = false;
}
async store(entry: MemoryEntry): Promise<void> {
if (!this.collection) {
throw new MemoryBackendError('Backend not initialized');
}
try {
// Use replaceOne for clean upsert - following claude-flow pattern
const document = { ...entry, updatedAt: new Date() };
delete (document as any)._id;
await this.collection.replaceOne(
{ id: entry.id },
document,
{ upsert: true }
);
} catch (error) {
throw new MemoryBackendError('Failed to store entry', { error, entryId: entry.id });
}
}
async retrieve(id: string): Promise<MemoryEntry | undefined> {
if (!this.collection) {
throw new MemoryBackendError('Backend not initialized');
}
try {
const entry = await this.collection.findOne({ id });
return entry || undefined;
} catch (error) {
throw new MemoryBackendError('Failed to retrieve entry', { error, entryId: id });
}
}
async update(_id: string, entry: MemoryEntry): Promise<void> {
await this.store(entry);
}
async delete(id: string): Promise<void> {
if (!this.collection) {
throw new MemoryBackendError('Backend not initialized');
}
try {
await this.collection.deleteOne({ id });
} catch (error) {
throw new MemoryBackendError('Failed to delete entry', { error, entryId: id });
}
}
async query(query: MemoryQuery): Promise<MemoryEntry[]> {
if (!this.collection) {
throw new MemoryBackendError('Backend not initialized');
}
try {
const filter: any = {};
if (query.projectPath) filter.projectPath = query.projectPath;
if (query.memoryType) filter.memoryType = query.memoryType;
if (query.tags && query.tags.length > 0) filter.tags = { $in: query.tags };
if (query.startTime || query.endTime) {
filter.timestamp = {};
if (query.startTime) filter.timestamp.$gte = query.startTime;
if (query.endTime) filter.timestamp.$lte = query.endTime;
}
if (query.search) {
filter.$or = [
{ searchableText: { $regex: query.search, $options: 'i' } },
{ 'content.projectName': { $regex: query.search, $options: 'i' } },
{ 'content.corePurpose': { $regex: query.search, $options: 'i' } }
];
}
const cursor = this.collection.find(filter)
.sort({ timestamp: -1 })
.limit(query.limit || 50);
if (query.offset) cursor.skip(query.offset);
return await cursor.toArray();
} catch (error) {
throw new MemoryBackendError('Failed to query entries', { error, query });
}
}
async getAllEntries(): Promise<MemoryEntry[]> {
if (!this.collection) {
throw new MemoryBackendError('Backend not initialized');
}
try {
return await this.collection.find({}).toArray();
} catch (error) {
throw new MemoryBackendError('Failed to get all entries', { error });
}
}
async getHealthStatus(): Promise<{
healthy: boolean;
error?: string;
metrics?: Record<string, number>;
}> {
if (!this.db || !this.collection) {
return { healthy: false, error: 'Backend not initialized' };
}
try {
await this.db.admin().ping();
let metrics: Record<string, number> | undefined;
if (this.config.enableMetrics !== false) {
const count = await this.collection.countDocuments();
const stats = await this.db.stats();
metrics = {
entryCount: count,
dbSizeBytes: stats.dataSize || 0,
indexSizeBytes: stats.indexSize || 0
};
}
return { healthy: true, metrics };
} catch (error) {
return {
healthy: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
private async createIndexes(): Promise<void> {
if (!this.collection) return;
try {
await this.collection.createIndex({ id: 1 }, { unique: true, background: true });
await this.collection.createIndex({ projectPath: 1, memoryType: 1 }, { background: true });
await this.collection.createIndex({ timestamp: -1 }, { background: true });
await this.collection.createIndex({ tags: 1 }, { background: true });
await this.collection.createIndex({ 'metadata.version': 1 }, { background: true });
} catch (error) {
// Index creation errors are non-fatal
}
}
}