@memory-bank/mcp
Version:
Memory-enabled Co-Pilot (MCP) server for managing project documentation and context
219 lines • 9.67 kB
JavaScript
import path from 'node:path';
import { DocumentPath } from '../../../domain/entities/DocumentPath.js';
import { DomainError } from '../../../shared/errors/DomainError.js';
import { InfrastructureError, InfrastructureErrorCodes } from '../../../shared/errors/InfrastructureError.js';
import { FileSystemMemoryBankRepositoryBase } from './FileSystemMemoryBankRepositoryBase.js';
import { DocumentOperations } from './DocumentOperations.js';
import { TagOperations } from './TagOperations.js';
import { PathOperations } from './PathOperations.js';
/**
* Component responsible for bulk operations
* Handles bulk retrieval, update, deletion of multiple documents
*/
export class BulkOperations extends FileSystemMemoryBankRepositoryBase {
basePath;
documentOps;
tagOps;
pathOps;
/**
* Constructor
* @param basePath Base path
* @param fileSystemService File system service
* @param configProvider Configuration provider
*/
constructor(basePath, fileSystemService, configProvider) {
super(fileSystemService, configProvider);
this.basePath = basePath;
this.documentOps = new DocumentOperations(basePath, fileSystemService, configProvider);
this.tagOps = new TagOperations(basePath, fileSystemService, configProvider);
this.pathOps = new PathOperations(basePath, fileSystemService, configProvider);
}
/**
* Get multiple documents in bulk
* @param paths Array of document paths to retrieve
* @returns Array of documents (including null for non-existent ones)
*/
async getDocuments(paths) {
try {
this.logDebug(`Bulk getting ${paths.length} documents`);
// Retrieve each document in parallel
const promises = paths.map(path => this.documentOps.getDocument(path));
return await Promise.all(promises);
}
catch (error) {
if (error instanceof DomainError || error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_READ_ERROR, `Failed to get multiple documents: ${error.message}`, { originalError: error });
}
}
/**
* Save multiple documents in bulk
* @param documents Array of documents to save
*/
async saveDocuments(documents) {
try {
this.logDebug(`Bulk saving ${documents.length} documents`);
// Save each document in parallel
const promises = documents.map(doc => this.documentOps.saveDocument(doc));
await Promise.all(promises);
// Update tag index
await this.tagOps.generateAndSaveTagIndex(documents);
this.logDebug(`Successfully saved ${documents.length} documents`);
}
catch (error) {
if (error instanceof DomainError || error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_WRITE_ERROR, `Failed to save multiple documents: ${error.message}`, { originalError: error });
}
}
/**
* Delete multiple documents in bulk
* @param paths Array of document paths to delete
* @returns Array of booleans indicating success for each deletion
*/
async deleteDocuments(paths) {
try {
this.logDebug(`Bulk deleting ${paths.length} documents`);
// Delete each document in parallel
const promises = paths.map(path => this.documentOps.deleteDocument(path));
const results = await Promise.all(promises);
// Update tag index if at least one deletion was successful
if (results.some(result => result)) {
// Get remaining documents to update tag index
const allPaths = await this.documentOps.listDocuments();
const allDocs = await this.getDocuments(allPaths.filter(p => !paths.some(deletedPath => deletedPath.value === p.value || deletedPath.toAlternateFormat().value === p.value)));
const validDocs = allDocs.filter((doc) => doc !== null);
await this.tagOps.generateAndSaveTagIndex(validDocs);
}
return results;
}
catch (error) {
if (error instanceof DomainError || error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_SYSTEM_ERROR, `Failed to delete multiple documents: ${error.message}`, { originalError: error });
}
}
/**
* Find documents by tags
* @param tags Array of tags to search for
* @param matchAll Whether all tags must match (AND) or any tag is sufficient (OR)
* @returns Array of matching documents
*/
async findDocumentsByTags(tags, matchAll = false) {
try {
this.logDebug(`Finding documents by ${tags.length} tags (matchAll: ${matchAll})`);
// First, get all document paths
const allPaths = await this.documentOps.listDocuments();
// Filter paths using the tag index
const allDocs = await this.getDocuments(allPaths);
const validDocs = allDocs.filter((doc) => doc !== null);
// Search using the tag index
const matchingPaths = await this.tagOps.findDocumentPathsByTagsUsingIndex(tags, validDocs, matchAll);
// Get documents for the matching paths
const matchingDocs = await this.getDocuments(matchingPaths);
// Return only non-null documents
return matchingDocs.filter((doc) => doc !== null);
}
catch (error) {
if (error instanceof DomainError || error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_READ_ERROR, `Failed to find documents by tags: ${error.message}`, { originalError: error });
}
}
/**
* List all documents
* @returns Array of all documents
*/
async getAllDocuments() {
try {
this.logDebug('Getting all documents');
// Get all paths
const allPaths = await this.documentOps.listDocuments();
// Get all documents
const allDocs = await this.getDocuments(allPaths);
// Return only non-null documents
return allDocs.filter((doc) => doc !== null);
}
catch (error) {
if (error instanceof DomainError || error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_READ_ERROR, `Failed to get all documents: ${error.message}`, { originalError: error });
}
}
/**
* Get all documents within a specific directory
* @param directoryPath Directory path
* @returns Array of documents within the directory
*/
async getDocumentsInDirectory(directoryPath) {
try {
this.logDebug(`Getting documents in directory: ${directoryPath}`);
// List files in the directory
const fileList = await this.pathOps.listFilesInDirectory(directoryPath, ['.json', '.md']);
// Convert file paths to DocumentPath
const paths = fileList.map(file => DocumentPath.create(file));
// Get the documents
const docs = await this.getDocuments(paths);
// Return only non-null documents
return docs.filter((doc) => doc !== null);
}
catch (error) {
if (error instanceof DomainError || error instanceof InfrastructureError) {
throw error;
}
throw new InfrastructureError(InfrastructureErrorCodes.FILE_READ_ERROR, `Failed to get documents in directory ${directoryPath}: ${error.message}`, { originalError: error });
}
}
/**
* Regenerate the tag index
* @returns true if successful
*/
async rebuildTagIndex() {
try {
this.logDebug('Rebuilding tag index');
// Get all documents
const docs = await this.getAllDocuments();
// Generate and save the tag index
await this.tagOps.generateAndSaveTagIndex(docs);
return true;
}
catch (error) {
this.logError('Failed to rebuild tag index', error);
return false;
}
}
/**
* Validate if the directory structure is valid
* @returns Validation result (true if successful)
*/
async validateStructure() {
try {
this.logDebug('Validating structure');
// Check if base directory exists
const baseDirExists = await this.directoryExists(this.basePath);
if (!baseDirExists) {
this.logDebug(`Base directory does not exist: ${this.basePath}`);
return false;
}
// Check if tags directory exists
const tagsDir = path.join(this.basePath, 'tags');
const tagsDirExists = await this.directoryExists(tagsDir);
if (!tagsDirExists) {
this.logDebug(`Tags directory does not exist: ${tagsDir}`);
// Create tags directory if it doesn't exist
await this.createDirectory(tagsDir);
}
return true;
}
catch (error) {
this.logError('Error validating structure', error);
return false;
}
}
}
//# sourceMappingURL=BulkOperations.js.map