@memory-bank/mcp
Version:
Memory-enabled Co-Pilot (MCP) server for managing project documentation and context
143 lines • 6.86 kB
JavaScript
import { applyPatch, createPatch } from 'rfc6902'; // Import rfc6902 functions
import { DomainError, DomainErrorCodes } from '../../shared/errors/DomainError.js';
import { JsonPatchOperation } from './JsonPatchOperation.js';
import { logger } from '../../shared/utils/logger.js';
/**
* Adapter class for integrating with the rfc6902 library.
* Implements the JsonPatchService interface.
*/
export class Rfc6902JsonPatchAdapter {
componentLogger = logger.withContext({ component: 'Rfc6902JsonPatchAdapter' });
/**
* Apply patch operations to a document using rfc6902.
* @param document Target document (will be cloned to ensure immutability).
* @param operations Array of operations to apply (JsonPatchOperation format).
* @returns New document after applying operations.
* @throws DomainError if an error occurs during operation application.
*/
apply(document, operations) {
const rfc6902Ops = this.convertOperationsToRfc6902(operations);
try {
// Use structuredClone for deep cloning (available in Node.js v17+)
const clonedDocument = structuredClone(document);
// applyPatch modifies the document in place and returns an array of errors
const errors = applyPatch(clonedDocument, rfc6902Ops);
// Check for errors
const firstError = errors.find(err => err !== null);
if (firstError) {
this.componentLogger.error('Error applying JSON Patch:', { errors });
throw new DomainError(DomainErrorCodes.JSON_PATCH_FAILED, `JSON patch operation failed: ${firstError.message || 'Unknown error'}`, {
errorType: firstError.constructor.name,
// MissingError has path property
...(firstError instanceof Error && 'path' in firstError && { path: firstError.path }),
// TestError has actual and expected properties
...(firstError instanceof Error && 'actual' in firstError && {
actual: firstError.actual,
expected: firstError.expected
})
});
}
// Return the modified clone
return clonedDocument;
}
catch (error) {
if (error instanceof DomainError) {
throw error; // Re-throw domain errors
}
this.componentLogger.error('Unexpected error applying JSON Patch:', { error });
this.handleLibraryError(error);
}
}
/**
* Validate patch operations against a document.
* Note: rfc6902 doesn't provide a direct validation function, so we apply the patch to a clone
* and check if any errors are returned.
* @param document Target document.
* @param operations Array of operations to validate (JsonPatchOperation format).
* @returns true if no errors are returned from applyPatch, false otherwise.
*/
validate(document, operations) {
const rfc6902Ops = this.convertOperationsToRfc6902(operations);
try {
// Try applying the patch to a clone to see if it throws
const clonedDocument = structuredClone(document);
const errors = applyPatch(clonedDocument, rfc6902Ops);
const valid = errors.every(err => err === null);
if (!valid) {
this.componentLogger.warn('JSON Patch validation failed:', { errors });
}
return valid;
}
catch (error) {
this.componentLogger.warn('JSON Patch validation failed (unexpected error):', { error });
return false; // Error thrown, assume invalid
}
}
/**
* Generate patch operations (diff) between two documents.
* @param source Source document.
* @param target Target document.
* @returns Array of JsonPatchOperation representing the diff.
*/
generatePatch(source, target) {
try {
// createPatch returns an array of operations
const diff = createPatch(source, target);
// Convert rfc6902 operations to JsonPatchOperation
return diff.map(op => this.convertFromRfc6902Operation(op));
}
catch (error) {
this.componentLogger.error('Error generating JSON Patch diff:', { error });
throw new DomainError(DomainErrorCodes.JSON_PATCH_FAILED, `Failed to generate patch diff: ${error.message || 'Unknown error'}`, { originalError: error });
}
}
/**
* Converts internal JsonPatchOperation format to rfc6902 format.
* @param operations Array of JsonPatchOperation.
* @returns Array of operations in rfc6902 format.
*/
convertOperationsToRfc6902(operations) {
return operations.map(op => {
// Ensure path and from are converted to strings
const rfcOp = { op: op.op, path: op.path.toString() };
if (op.value !== undefined) {
rfcOp.value = op.value;
}
if (op.from !== undefined) {
// Ensure 'from' path is also converted to string
rfcOp.from = op.from.toString();
}
// Ensure the final object conforms to Rfc6902Operation type as much as possible
return rfcOp;
});
}
/**
* Converts rfc6902 operation format back to internal JsonPatchOperation format.
* @param rfcOp Operation in rfc6902 format.
* @returns New JsonPatchOperation instance.
*/
convertFromRfc6902Operation(rfcOp) {
return JsonPatchOperation.create(rfcOp.op, rfcOp.path, rfcOp.value, rfcOp.from);
}
/**
* Convert library error to domain-specific error.
* @param error Error that occurred.
* @throws DomainError Converted domain error.
*/
handleLibraryError(error) {
const message = error instanceof Error ? error.message : 'Unknown JSON Patch error';
// Basic error mapping (can be expanded based on rfc6902 error types/messages)
if (message.toLowerCase().includes('test')) {
throw new DomainError(DomainErrorCodes.TEST_FAILED, `Test failed: ${message}`, { originalError: error });
}
// Check for common pointer/path errors
if (message.toLowerCase().includes('invalid pointer') ||
message.toLowerCase().includes('path') ||
message.toLowerCase().includes('member')) {
throw new DomainError(DomainErrorCodes.PATH_NOT_FOUND, `Invalid path or pointer: ${message}`, { originalError: error });
}
// Generic fallback
throw new DomainError(DomainErrorCodes.JSON_PATCH_FAILED, `JSON patch operation failed: ${message}`, { originalError: error });
}
}
//# sourceMappingURL=Rfc6902JsonPatchAdapter.js.map