UNPKG

@myea/aem-mcp-handler

Version:

Advanced AEM MCP request handler with intelligent search, multi-locale support, and comprehensive content management capabilities

164 lines (163 loc) 6.13 kB
export class AEMOperationError extends Error { code; details; recoverable; retryAfter; constructor(error) { super(error.message); this.name = 'AEMOperationError'; this.code = error.code; this.details = error.details; this.recoverable = error.recoverable; this.retryAfter = error.retryAfter; } } export const AEM_ERROR_CODES = { // Network & Connection Errors CONNECTION_FAILED: 'CONNECTION_FAILED', TIMEOUT: 'TIMEOUT', AUTHENTICATION_FAILED: 'AUTHENTICATION_FAILED', UNAUTHORIZED: 'UNAUTHORIZED', // Validation Errors INVALID_PATH: 'INVALID_PATH', INVALID_COMPONENT_TYPE: 'INVALID_COMPONENT_TYPE', INVALID_LOCALE: 'INVALID_LOCALE', INVALID_PARAMETERS: 'INVALID_PARAMETERS', // Content Errors RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND', COMPONENT_NOT_FOUND: 'COMPONENT_NOT_FOUND', PAGE_NOT_FOUND: 'PAGE_NOT_FOUND', // Operation Errors UPDATE_FAILED: 'UPDATE_FAILED', VALIDATION_FAILED: 'VALIDATION_FAILED', REPLICATION_FAILED: 'REPLICATION_FAILED', QUERY_FAILED: 'QUERY_FAILED', // System Errors INSUFFICIENT_PERMISSIONS: 'INSUFFICIENT_PERMISSIONS', SYSTEM_ERROR: 'SYSTEM_ERROR', RATE_LIMITED: 'RATE_LIMITED', }; /** * Create standardized error responses for AEM operations */ export function createAEMError(code, message, details, recoverable = false, retryAfter) { return new AEMOperationError({ code, message, details, recoverable, retryAfter }); } /** * Handle HTTP errors from AEM responses */ export function handleAEMHttpError(error, operation) { if (error.response) { const status = error.response.status; const data = error.response.data; switch (status) { case 401: return createAEMError(AEM_ERROR_CODES.AUTHENTICATION_FAILED, 'Authentication failed. Check AEM credentials.', { status, data }); case 403: return createAEMError(AEM_ERROR_CODES.INSUFFICIENT_PERMISSIONS, 'Insufficient permissions for this operation.', { status, data, operation }); case 404: return createAEMError(AEM_ERROR_CODES.RESOURCE_NOT_FOUND, 'Resource not found in AEM.', { status, data, operation }); case 429: const retryAfter = error.response.headers['retry-after']; return createAEMError(AEM_ERROR_CODES.RATE_LIMITED, 'Rate limit exceeded. Please try again later.', { status, data }, true, retryAfter ? parseInt(retryAfter) * 1000 : 60000); case 500: case 502: case 503: return createAEMError(AEM_ERROR_CODES.SYSTEM_ERROR, 'AEM system error. Please try again later.', { status, data }, true, 30000); default: return createAEMError(AEM_ERROR_CODES.SYSTEM_ERROR, `HTTP ${status}: ${data?.message || 'Unknown error'}`, { status, data, operation }); } } else if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') { return createAEMError(AEM_ERROR_CODES.CONNECTION_FAILED, 'Cannot connect to AEM instance. Check host and network.', { originalError: error.message }, true, 5000); } else if (error.code === 'ETIMEDOUT') { return createAEMError(AEM_ERROR_CODES.TIMEOUT, 'Request to AEM timed out.', { originalError: error.message }, true, 10000); } else { return createAEMError(AEM_ERROR_CODES.SYSTEM_ERROR, `Unexpected error during ${operation}: ${error.message}`, { originalError: error.message }); } } /** * Safe execution wrapper with retry logic */ export async function safeExecute(operation, operationName, maxRetries = 3) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error instanceof AEMOperationError ? error : handleAEMHttpError(error, operationName); // Don't retry if error is not recoverable if (!lastError.recoverable || attempt === maxRetries) { break; } // Wait before retrying const delay = lastError.retryAfter || Math.pow(2, attempt) * 1000; console.warn(`[${operationName}] Attempt ${attempt} failed, retrying in ${delay}ms:`, lastError.message); await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError; } /** * Validate component operation parameters */ export function validateComponentOperation(locale, pagePath, component, props) { const errors = []; if (!locale || typeof locale !== 'string') { errors.push('Locale is required and must be a string'); } if (!pagePath || typeof pagePath !== 'string') { errors.push('Page path is required and must be a string'); } else if (!pagePath.startsWith('/content')) { errors.push('Page path must start with /content'); } if (!component || typeof component !== 'string') { errors.push('Component type is required and must be a string'); } if (!props || typeof props !== 'object') { errors.push('Component properties are required and must be an object'); } if (errors.length > 0) { throw createAEMError(AEM_ERROR_CODES.INVALID_PARAMETERS, 'Invalid component operation parameters', { errors }); } } /** * Create standardized success response */ export function createSuccessResponse(data, operation) { return { success: true, operation, timestamp: new Date().toISOString(), data }; } /** * Create standardized error response */ export function createErrorResponse(error, operation) { return { success: false, operation, timestamp: new Date().toISOString(), error: { code: error.code, message: error.message, details: error.details, recoverable: error.recoverable, retryAfter: error.retryAfter } }; }