UNPKG

suitecrm-mcp-server

Version:

Model Context Protocol server for SuiteCRM integration with natural language SQL reporting

254 lines 9.85 kB
"use strict"; /** * Module management service for SuiteCRM */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ModuleService = void 0; const zod_1 = require("zod"); const types_1 = require("../types"); const http_1 = require("../utils/http"); const logger_1 = require("../utils/logger"); // Validation schemas const ModuleInfoSchema = zod_1.z.object({ name: zod_1.z.string().min(1, 'Module name is required'), label: zod_1.z.string().min(1, 'Module label is required'), type: zod_1.z.string().optional(), fields: zod_1.z.array(zod_1.z.string()).optional(), relationships: zod_1.z.array(zod_1.z.string()).optional() }); function safeJsonParse(data) { if (typeof data === 'string') { try { return JSON.parse(data); } catch { return data; } } return data; } class ModuleService { httpClient; schemaCache = new Map(); cacheTtl = 30 * 60 * 1000; // 30 minutes constructor(baseUrl, timeout = 30000) { this.httpClient = new http_1.HttpClient({ baseURL: baseUrl, timeout }); } /** * Get available modules from SuiteCRM */ async getModules(accessToken) { try { logger_1.logger.info('Fetching available modules'); this.httpClient.setAuthToken(accessToken); const response = await this.httpClient.get('/Api/V8/custom/modules'); if (response.status !== 200) { throw new types_1.SuiteCRMError(`Failed to fetch modules: ${response.status} ${response.statusText}`); } // Handle SuiteCRM's actual response structure const crm = response.data; const data = safeJsonParse(crm.data); const modules = []; for (const moduleData of data) { try { const validatedModule = ModuleInfoSchema.parse(moduleData); // Transform to remove undefined optional properties const moduleInfo = { name: validatedModule.name, label: validatedModule.label, type: validatedModule.type || 'default', }; if (validatedModule.fields) { moduleInfo.fields = validatedModule.fields; } if (validatedModule.relationships) { moduleInfo.relationships = validatedModule.relationships; } modules.push(moduleInfo); } catch (validationError) { logger_1.logger.warn('Invalid module data', { module: moduleData.name || 'unknown', error: validationError instanceof Error ? validationError.message : 'Unknown validation error' }); } } logger_1.logger.info('Successfully fetched modules', { count: modules.length }); return modules; } catch (error) { logger_1.logger.error('Failed to fetch modules', {}, error instanceof Error ? error : new Error(String(error))); if (error instanceof types_1.SuiteCRMError || error instanceof types_1.ValidationError) { throw error; } throw new types_1.SuiteCRMError(`Failed to fetch modules: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get detailed schema for a specific module */ async getModuleSchema(accessToken, moduleName) { try { // Check cache first const cacheKey = `${moduleName}:${accessToken.substring(0, 8)}`; const cached = this.schemaCache.get(cacheKey); if (cached && Date.now() < cached.expiresAt) { logger_1.logger.info('Using cached module schema', { module: moduleName }); return cached.schema; } logger_1.logger.info('Fetching module schema', { module: moduleName }); this.httpClient.setAuthToken(accessToken); const response = await this.httpClient.get(`/Api/V8/custom/modules/${moduleName}/schema`); if (response.status !== 200) { throw new types_1.SuiteCRMError(`Failed to fetch module schema: ${response.status} ${response.statusText}`); } // Handle SuiteCRM's actual response structure const crm = response.data; const data = safeJsonParse(crm.data); const fieldsObj = data?.fields || {}; const relationshipsObj = data?.relationships || {}; // Convert fields object to array const fields = Object.entries(fieldsObj).map(([name, def]) => ({ name, label: def.label || name, type: def.type || 'string', required: !!def.required, readonly: false // You may want to infer this if possible // Add other properties as needed })); // Convert relationships object to array const relationships = Object.entries(relationshipsObj).map(([name, def]) => ({ name, label: name, type: def.relationship_type || 'one-to-many', module: def.rhs_module || '', key: def.rhs_key || '', foreign_key: def.lhs_key || '' // Add other properties as needed })); const schema = { name: moduleName, label: moduleName, // Or use a better label if available fields, relationships, indexes: [] }; // Cache the schema this.schemaCache.set(cacheKey, { schema, expiresAt: Date.now() + this.cacheTtl }); logger_1.logger.info('Successfully fetched module schema', { module: moduleName, fields: schema.fields.length, relationships: schema.relationships.length }); return schema; } catch (error) { logger_1.logger.error('Failed to fetch module schema', { module: moduleName }, error instanceof Error ? error : new Error(String(error))); if (error instanceof types_1.SuiteCRMError || error instanceof types_1.ValidationError) { throw error; } throw new types_1.SuiteCRMError(`Failed to fetch module schema for ${moduleName}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get multiple module schemas at once */ async getModuleSchemas(accessToken, moduleNames) { const schemas = {}; const errors = []; // Fetch schemas in parallel with error handling const promises = moduleNames.map(async (moduleName) => { try { const schema = await this.getModuleSchema(accessToken, moduleName); schemas[moduleName] = schema; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; errors.push(`${moduleName}: ${errorMessage}`); logger_1.logger.error('Failed to fetch schema for module', { module: moduleName }, error instanceof Error ? error : new Error(errorMessage)); } }); await Promise.all(promises); if (errors.length > 0) { logger_1.logger.warn('Some module schemas failed to load', { errors }); } return schemas; } /** * Get field information for a specific module */ async getModuleFields(accessToken, moduleName) { const schema = await this.getModuleSchema(accessToken, moduleName); return schema.fields; } /** * Get relationship information for a specific module */ async getModuleRelationships(accessToken, moduleName) { const schema = await this.getModuleSchema(accessToken, moduleName); return schema.relationships; } /** * Check if a module exists */ async moduleExists(accessToken, moduleName) { try { await this.getModuleSchema(accessToken, moduleName); return true; } catch (error) { return false; } } /** * Get cache statistics */ getCacheStats() { let expired = 0; let valid = 0; for (const [_, cached] of this.schemaCache.entries()) { if (Date.now() >= cached.expiresAt) { expired++; } else { valid++; } } return { total: this.schemaCache.size, expired, valid }; } /** * Clear schema cache */ clearCache() { const count = this.schemaCache.size; this.schemaCache.clear(); logger_1.logger.info('Module schema cache cleared', { schemasCleared: count }); } /** * Clear cache for specific module */ clearModuleCache(moduleName) { const keysToDelete = []; for (const [key, _] of this.schemaCache.entries()) { if (key.startsWith(`${moduleName}:`)) { keysToDelete.push(key); } } keysToDelete.forEach(key => this.schemaCache.delete(key)); if (keysToDelete.length > 0) { logger_1.logger.info('Module cache cleared', { module: moduleName, keysCleared: keysToDelete.length }); } } } exports.ModuleService = ModuleService; //# sourceMappingURL=modules.js.map