UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

175 lines (172 loc) • 6.08 kB
/** * ViewOnlyFieldResolver - Resolves fields ONLY from views * * NO FALLBACKS - If a field isn't in a view, it's not queryable. * This replaces the complex FieldLocalityResolver with a simple, fast view-based system. */ import { VIEW_ONLY_FIELD_MAPPINGS } from '../generated/ViewOnlyFieldMappings.generated.js'; import { getLogger } from '../../logging/Logger.js'; const logger = getLogger(); export class ViewOnlyFieldResolver { fieldMappings; constructor() { this.fieldMappings = new Map(Object.entries(VIEW_ONLY_FIELD_MAPPINGS)); logger.info(`ViewOnlyFieldResolver initialized with ${this.fieldMappings.size} field mappings`); logger.info('🚫 NO LEGACY FALLBACKS - Views or nothing!'); } /** * Resolve a field to its view location * @throws Error if field is not available in any view */ resolve(fieldName) { const mapping = this.fieldMappings.get(fieldName); if (!mapping) { const availableFields = this.getSimilarFields(fieldName); const suggestion = availableFields.length > 0 ? `\\nDid you mean: ${availableFields.slice(0, 3).join(', ')}?` : ''; throw new Error(`Field '${fieldName}' is not available in any view. Cannot process query.${suggestion}\\n` + `This is a VIEW-ONLY system. No fallbacks to complex JOINs.`); } logger.debug(`Field '${fieldName}' resolved to view: ${mapping.view.viewName}.${mapping.view.columnName}`); return { viewName: mapping.view.viewName, columnName: mapping.view.columnName, isPreComputed: mapping.view.preComputed, dataType: mapping.view.dataType }; } /** * Check if a field is available */ isFieldAvailable(fieldName) { return this.fieldMappings.has(fieldName); } /** * Get all available field names */ getAllFields() { return Array.from(this.fieldMappings.keys()); } /** * Get fields for a specific view */ getFieldsForView(viewName) { const fields = []; for (const [fieldName, mapping] of this.fieldMappings) { if (mapping.view.viewName === viewName) { fields.push(fieldName); } } return fields; } /** * Get field metadata */ getFieldMetadata(fieldName) { const mapping = this.fieldMappings.get(fieldName); if (!mapping) { throw new Error(`Field '${fieldName}' not found`); } return { ...mapping.metadata, view: mapping.view.viewName, column: mapping.view.columnName, preComputed: mapping.view.preComputed }; } /** * Find similar field names for suggestions */ getSimilarFields(searchTerm) { const term = searchTerm.toLowerCase(); const matches = []; for (const field of this.fieldMappings.keys()) { const fieldLower = field.toLowerCase(); // Exact substring match if (fieldLower.includes(term)) { matches.push({ field, score: 1 }); } // Starts with search term else if (fieldLower.startsWith(term)) { matches.push({ field, score: 0.8 }); } // Contains all characters in order else if (this.fuzzyMatch(term, fieldLower)) { matches.push({ field, score: 0.5 }); } } // Sort by score and return field names return matches .sort((a, b) => b.score - a.score) .map(m => m.field); } /** * Simple fuzzy matching */ fuzzyMatch(needle, haystack) { let hIndex = 0; for (const char of needle) { hIndex = haystack.indexOf(char, hIndex); if (hIndex === -1) return false; hIndex++; } return true; } /** * Get statistics about field coverage */ getStats() { const stats = { totalFields: this.fieldMappings.size, preComputedFields: 0, views: {}, dataTypes: {} }; for (const mapping of this.fieldMappings.values()) { // Count pre-computed fields if (mapping.view.preComputed) { stats.preComputedFields++; } // Count fields per view stats.views[mapping.view.viewName] = (stats.views[mapping.view.viewName] || 0) + 1; // Count by data type stats.dataTypes[mapping.view.dataType] = (stats.dataTypes[mapping.view.dataType] || 0) + 1; } return stats; } /** * Generate a WHERE clause for a field */ generateWhereClause(fieldName, operator, value) { const resolution = this.resolve(fieldName); // Format value based on data type let formattedValue = value; if (resolution.dataType === 'string' || resolution.dataType === 'identifier') { formattedValue = `'${value}'`; } else if (resolution.dataType === 'boolean') { formattedValue = value ? 1 : 0; } return `${resolution.viewName}.${resolution.columnName} ${operator} ${formattedValue}`; } /** * Get a diagnostic report */ getDiagnostics() { const stats = this.getStats(); return ` ViewOnlyFieldResolver Diagnostics ================================= Total Fields: ${stats.totalFields} Pre-computed Fields: ${stats.preComputedFields} (${Math.round(stats.preComputedFields / stats.totalFields * 100)}%) Fields by View: ${Object.entries(stats.views).map(([view, count]) => ` - ${view}: ${count} fields`).join('\\n')} Fields by Data Type: ${Object.entries(stats.dataTypes).map(([type, count]) => ` - ${type}: ${count} fields`).join('\\n')} 🚫 NO LEGACY SUPPORT - Views only! `.trim(); } } //# sourceMappingURL=ViewOnlyFieldResolver.js.map