@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
175 lines (172 loc) ⢠6.08 kB
JavaScript
/**
* 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