@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
738 lines • 29.8 kB
JavaScript
import { getLogger } from '../logging/Logger.js';
import { MappingConfidence } from './types.js';
/**
* COMPLETE IntelligentFieldMapper Implementation
*
* STATUS: 100% COMPLETE - All 26 database entities mapped
* DATE: July 2, 2025
*
* This is the COMPLETE implementation with ALL entity schemas from the database.
* Replaces the incomplete version that only had 3 entities.
*/
export class IntelligentFieldMapper {
sqliteEngine;
schemaMap;
fieldCache;
joinPathCache;
constructor(sqliteEngine) {
this.sqliteEngine = sqliteEngine;
this.schemaMap = this.initializeSchemaMap();
this.fieldCache = new Map();
this.joinPathCache = new Map();
getLogger().info({
totalSchemas: this.schemaMap.size,
entities: Array.from(this.schemaMap.keys())
}, 'IntelligentFieldMapper initialized with COMPLETE schema definitions');
}
/**
* Map a field reference to its database location
*/
mapField(entity, field) {
const cacheKey = `${entity}.${field}`;
// Check cache first
if (this.fieldCache.has(cacheKey)) {
const cached = this.fieldCache.get(cacheKey);
return {
success: true,
table: cached.table,
column: cached.column,
isJsonPath: cached.isJsonPath,
jsonPath: cached.jsonPath,
confidence: MappingConfidence.HIGH
};
}
// Get schema for entity
const schema = this.schemaMap.get(entity);
if (!schema) {
return {
success: false,
error: `Unknown entity: ${entity}. Available entities: ${Array.from(this.schemaMap.keys()).join(', ')}`,
confidence: MappingConfidence.NONE
};
}
// Try direct column mapping
if (schema.columns.includes(field)) {
const fieldInfo = {
table: schema.table,
column: field,
isJsonPath: false,
dataType: this.inferDataType(field)
};
this.fieldCache.set(cacheKey, fieldInfo);
return {
success: true,
table: schema.table,
column: field,
isJsonPath: false,
confidence: MappingConfidence.HIGH
};
}
// Try JSON path mapping
const jsonMapping = this.tryJsonMapping(schema, field);
if (jsonMapping.success) {
this.fieldCache.set(cacheKey, {
table: schema.table,
column: jsonMapping.column,
isJsonPath: true,
jsonPath: jsonMapping.jsonPath,
dataType: 'json'
});
return jsonMapping;
}
// Try fuzzy matching
const fuzzyMatch = this.findSimilarField(field, schema.columns);
if (fuzzyMatch) {
return {
success: false,
error: `Field '${field}' not found. Did you mean '${fuzzyMatch}'?`,
suggestion: fuzzyMatch,
confidence: MappingConfidence.LOW
};
}
return {
success: false,
error: `Field '${field}' not found in entity '${entity}'`,
confidence: MappingConfidence.NONE
};
}
/**
* Get join path between two entities
*/
getJoinPath(fromEntity, toEntity) {
const cacheKey = `${fromEntity}->${toEntity}`;
if (this.joinPathCache.has(cacheKey)) {
return this.joinPathCache.get(cacheKey);
}
const fromSchema = this.schemaMap.get(fromEntity);
const toSchema = this.schemaMap.get(toEntity);
if (!fromSchema || !toSchema) {
return [];
}
// Direct join?
if (fromSchema.joins && fromSchema.joins[toEntity]) {
const joinInfo = {
table: toSchema.table,
on: fromSchema.joins[toEntity].on,
type: fromSchema.joins[toEntity].type || 'INNER',
fromTable: fromSchema.table,
toTable: toSchema.table,
joinType: fromSchema.joins[toEntity].type || 'INNER',
joinCondition: fromSchema.joins[toEntity].on
};
this.joinPathCache.set(cacheKey, [joinInfo]);
return [joinInfo];
}
// Reverse join?
if (toSchema.joins && toSchema.joins[fromEntity]) {
const reverseJoin = toSchema.joins[fromEntity];
const joinInfo = {
table: toSchema.table,
on: reverseJoin.on,
type: 'LEFT', // Reverse joins are typically LEFT
fromTable: fromSchema.table,
toTable: toSchema.table,
joinType: 'LEFT',
joinCondition: reverseJoin.on
};
this.joinPathCache.set(cacheKey, [joinInfo]);
return [joinInfo];
}
// TODO: Implement multi-hop join discovery
return [];
}
/**
* Try to map field as JSON path
*/
tryJsonMapping(schema, field) {
// Fields that should never be JSON-mapped because they exist in related entities
const relatedEntityFields = [
'environment_key', // This should come from flag_environments table, not JSON
'environment' // This should map to environment_key from flag_environments table
];
if (relatedEntityFields.includes(field)) {
return {
success: false,
confidence: MappingConfidence.NONE
};
}
// Check if field looks like a JSON path
if (field.includes('.') || field.includes('[')) {
// For now, assume data_json column
if (schema.jsonColumns && schema.jsonColumns.includes('data_json')) {
return {
success: true,
table: schema.table,
column: 'data_json',
isJsonPath: true,
jsonPath: `$.${field}`,
confidence: MappingConfidence.MEDIUM
};
}
}
// Try common JSON field patterns
const jsonPatterns = [
{ column: 'data_json', prefix: '' },
{ column: 'conditions', prefix: '' },
{ column: 'variables', prefix: '' },
{ column: 'results_json', prefix: 'results' },
{ column: 'permissions_json', prefix: 'permissions' }
];
for (const pattern of jsonPatterns) {
if (schema.jsonColumns && schema.jsonColumns.includes(pattern.column)) {
const jsonPath = pattern.prefix
? `$.${pattern.prefix}.${field}`
: `$.${field}`;
return {
success: true,
table: schema.table,
column: pattern.column,
isJsonPath: true,
jsonPath,
confidence: MappingConfidence.MEDIUM
};
}
}
return {
success: false,
confidence: MappingConfidence.NONE
};
}
/**
* Find similar field name using fuzzy matching
*/
findSimilarField(field, candidates) {
const normalized = field.toLowerCase().replace(/_/g, '');
for (const candidate of candidates) {
const candidateNorm = candidate.toLowerCase().replace(/_/g, '');
// Exact match after normalization
if (normalized === candidateNorm) {
return candidate;
}
// Substring match
if (candidateNorm.includes(normalized) || normalized.includes(candidateNorm)) {
return candidate;
}
}
return null;
}
/**
* Infer data type from field name
*/
inferDataType(field) {
const patterns = [
{ pattern: /_id$|^id$/, type: 'string' },
{ pattern: /_at$|_time$|created|modified|updated/, type: 'datetime' },
{ pattern: /count|total|amount|percentage/, type: 'number' },
{ pattern: /^is_|_enabled$|archived|active/, type: 'boolean' },
{ pattern: /name|description|key|type|status/, type: 'string' },
{ pattern: /_json$|conditions|variables/, type: 'json' }
];
for (const { pattern, type } of patterns) {
if (pattern.test(field)) {
return type;
}
}
return 'string';
}
/**
* Initialize the COMPLETE schema map with ALL database entities
* STATUS: 100% COMPLETE - All 26 entities mapped
*/
initializeSchemaMap() {
const schemaMap = new Map();
// 1. Projects schema
schemaMap.set('projects', {
table: 'projects',
columns: ['id', 'name', 'description', 'platform', 'status', 'account_id', 'is_flags_enabled', 'archived', 'created_at', 'last_modified', 'data_json'],
jsonColumns: ['data_json'],
joins: {
flags: {
table: 'flags',
on: 'projects.id = flags.project_id',
type: 'LEFT'
},
experiments: {
table: 'experiments',
on: 'projects.id = experiments.project_id',
type: 'LEFT'
},
environments: {
table: 'environments',
on: 'projects.id = environments.project_id',
type: 'LEFT'
}
}
});
// 2. Environments schema
schemaMap.set('environments', {
table: 'environments',
columns: ['project_id', 'key', 'name', 'is_primary', 'priority', 'archived', 'data_json'],
jsonColumns: ['data_json'],
joins: {
projects: {
table: 'projects',
on: 'environments.project_id = projects.id',
type: 'INNER'
},
flag_environments: {
table: 'flag_environments',
on: 'environments.project_id = flag_environments.project_id AND environments.key = flag_environments.environment_key',
type: 'LEFT'
}
}
});
// 3. Flags schema (already exists - keeping for completeness)
schemaMap.set('flags', {
table: 'flags',
columns: ['project_id', 'key', 'id', 'name', 'description', 'archived', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['data_json'],
joins: {
flag_environments: {
table: 'flag_environments',
on: 'flags.project_id = flag_environments.project_id AND flags.key = flag_environments.flag_key',
type: 'LEFT'
},
variations: {
table: 'variations',
on: 'flags.project_id = variations.project_id AND flags.key = variations.flag_key',
type: 'LEFT'
},
rules: {
table: 'rules',
on: 'flags.project_id = rules.project_id AND flags.key = rules.flag_key',
type: 'LEFT'
},
rulesets: {
table: 'rulesets',
on: 'flags.project_id = rulesets.project_id AND flags.key = rulesets.flag_key',
type: 'LEFT'
},
variable_definitions: {
table: 'variable_definitions',
on: 'flags.project_id = variable_definitions.project_id AND flags.key = variable_definitions.flag_key',
type: 'LEFT'
}
}
});
// 4. Flag Environments schema
schemaMap.set('flag_environments', {
table: 'flag_environments',
columns: ['project_id', 'flag_key', 'environment_key', 'enabled', 'archived', 'rules_summary', 'data_json'],
jsonColumns: ['rules_summary', 'data_json'],
joins: {
flags: {
table: 'flags',
on: 'flag_environments.project_id = flags.project_id AND flag_environments.flag_key = flags.key',
type: 'INNER'
},
environments: {
table: 'environments',
on: 'flag_environments.project_id = environments.project_id AND flag_environments.environment_key = environments.key',
type: 'INNER'
}
}
});
// 5. Variations schema
schemaMap.set('variations', {
table: 'variations',
columns: ['id', 'key', 'project_id', 'flag_key', 'name', 'description', 'enabled', 'archived', 'variables', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['variables', 'data_json'],
joins: {
flags: {
table: 'flags',
on: 'variations.project_id = flags.project_id AND variations.flag_key = flags.key',
type: 'INNER'
}
}
});
// 6. Variable Definitions schema
schemaMap.set('variable_definitions', {
table: 'variable_definitions',
columns: ['key', 'project_id', 'flag_key', 'description', 'type', 'default_value', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['data_json'],
joins: {
flags: {
table: 'flags',
on: 'variable_definitions.project_id = flags.project_id AND variable_definitions.flag_key = flags.key',
type: 'INNER'
}
}
});
// 7. Rulesets schema
schemaMap.set('rulesets', {
table: 'rulesets',
columns: ['id', 'project_id', 'flag_key', 'environment_key', 'enabled', 'rules_count', 'revision', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['data_json'],
joins: {
flags: {
table: 'flags',
on: 'rulesets.project_id = flags.project_id AND rulesets.flag_key = flags.key',
type: 'INNER'
},
rules: {
table: 'rules',
on: 'rulesets.project_id = rules.project_id AND rulesets.flag_key = rules.flag_key AND rulesets.environment_key = rules.environment_key',
type: 'LEFT'
}
}
});
// 8. Rules schema
schemaMap.set('rules', {
table: 'rules',
columns: ['id', 'key', 'project_id', 'flag_key', 'environment_key', 'type', 'name', 'audience_conditions', 'percentage_included', 'variations', 'metrics', 'enabled', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['audience_conditions', 'variations', 'metrics', 'data_json'],
joins: {
rulesets: {
table: 'rulesets',
on: 'rules.project_id = rulesets.project_id AND rules.flag_key = rulesets.flag_key AND rules.environment_key = rulesets.environment_key',
type: 'INNER'
},
flags: {
table: 'flags',
on: 'rules.project_id = flags.project_id AND rules.flag_key = flags.key',
type: 'INNER'
}
}
});
// 9. Changes schema
schemaMap.set('changes', {
table: 'changes',
columns: ['id', 'project_id', 'entity_type', 'entity_id', 'entity_key', 'action', 'user_id', 'user_email', 'timestamp', 'details', 'data_json'],
jsonColumns: ['details', 'data_json'],
joins: {
projects: {
table: 'projects',
on: 'changes.project_id = projects.id',
type: 'INNER'
}
}
});
// 10. Reports schema
schemaMap.set('reports', {
table: 'reports',
columns: ['id', 'project_id', 'flag_key', 'rule_key', 'environment_key', 'report_type', 'start_time', 'end_time', 'data_json'],
jsonColumns: ['data_json'],
joins: {
flags: {
table: 'flags',
on: 'reports.project_id = flags.project_id AND reports.flag_key = flags.key',
type: 'LEFT'
}
}
});
// 11. Features schema
schemaMap.set('features', {
table: 'features',
columns: ['id', 'project_id', 'key', 'name', 'description', 'archived', 'created', 'last_modified', 'variable_definitions', 'data_json'],
jsonColumns: ['variable_definitions', 'data_json'],
joins: {
projects: {
table: 'projects',
on: 'features.project_id = projects.id',
type: 'INNER'
}
}
});
// 12. Experiments schema (already exists - keeping for completeness)
schemaMap.set('experiments', {
table: 'experiments',
columns: ['id', 'project_id', 'name', 'description', 'status', 'type', 'flag_key', 'environment', 'archived', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['data_json'],
joins: {
results: {
table: 'experiment_results',
on: 'experiments.id = experiment_results.experiment_id',
type: 'LEFT'
},
campaigns: {
table: 'campaigns',
on: 'JSON_EXTRACT(experiments.data_json, "$.campaign_id") = campaigns.id',
type: 'LEFT'
},
audiences: {
table: 'audiences',
on: 'EXISTS (SELECT 1 FROM json_each(experiments.data_json, \'$.audience_conditions\') WHERE value = audiences.id)',
type: 'LEFT'
},
projects: {
table: 'projects',
on: 'experiments.project_id = projects.id',
type: 'INNER'
},
flags: {
table: 'flags',
on: 'experiments.flag_key = flags.key AND experiments.project_id = flags.project_id',
type: 'LEFT'
}
}
});
// 13. Experiment Results schema
schemaMap.set('experiment_results', {
table: 'experiment_results',
columns: ['id', 'experiment_id', 'project_id', 'confidence_level', 'use_stats_engine', 'stats_engine_version', 'baseline_count', 'treatment_count', 'total_count', 'start_time', 'last_update', 'results_json', 'reach_json', 'stats_config_json', 'data_json'],
jsonColumns: ['results_json', 'reach_json', 'stats_config_json', 'data_json'],
joins: {
experiments: {
table: 'experiments',
on: 'experiment_results.experiment_id = experiments.id',
type: 'INNER'
},
projects: {
table: 'projects',
on: 'experiment_results.project_id = projects.id',
type: 'INNER'
}
}
});
// 14. Campaigns schema
schemaMap.set('campaigns', {
table: 'campaigns',
columns: ['id', 'project_id', 'name', 'description', 'holdback', 'archived', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['data_json'],
joins: {
projects: {
table: 'projects',
on: 'campaigns.project_id = projects.id',
type: 'INNER'
},
experiments: {
table: 'experiments',
on: 'JSON_EXTRACT(experiments.data_json, "$.campaign_id") = campaigns.id',
type: 'LEFT'
}
}
});
// 15. Pages schema
schemaMap.set('pages', {
table: 'pages',
columns: ['id', 'project_id', 'name', 'edit_url', 'activation_mode', 'activation_code', 'conditions', 'archived', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['conditions', 'data_json'],
joins: {
projects: {
table: 'projects',
on: 'pages.project_id = projects.id',
type: 'INNER'
}
}
});
// 16. Audiences schema (already exists - keeping for completeness)
schemaMap.set('audiences', {
table: 'audiences',
columns: ['id', 'project_id', 'name', 'description', 'conditions', 'archived', 'created_time', 'last_modified', 'data_json'],
jsonColumns: ['conditions', 'data_json'],
joins: {
experiments: {
table: 'experiments',
on: 'EXISTS (SELECT 1 FROM json_each(experiments.data_json, \'$.audience_conditions\') WHERE value = audiences.id)',
type: 'LEFT'
},
projects: {
table: 'projects',
on: 'audiences.project_id = projects.id',
type: 'INNER'
}
}
});
// 17. Attributes schema
schemaMap.set('attributes', {
table: 'attributes',
columns: ['id', 'project_id', 'key', 'name', 'condition_type', 'archived', 'last_modified', 'data_json'],
jsonColumns: ['data_json'],
joins: {
projects: {
table: 'projects',
on: 'attributes.project_id = projects.id',
type: 'INNER'
}
}
});
// 18. Events schema
schemaMap.set('events', {
table: 'events',
columns: ['id', 'project_id', 'key', 'name', 'description', 'event_type', 'category', 'archived', 'created_time', 'data_json'],
jsonColumns: ['data_json'],
joins: {
projects: {
table: 'projects',
on: 'events.project_id = projects.id',
type: 'INNER'
}
}
});
// 19. Collaborators schema
schemaMap.set('collaborators', {
table: 'collaborators',
columns: ['user_id', 'project_id', 'email', 'name', 'role', 'permissions_json', 'invited_at', 'last_seen_at', 'data_json'],
jsonColumns: ['permissions_json', 'data_json'],
joins: {
projects: {
table: 'projects',
on: 'collaborators.project_id = projects.id',
type: 'INNER'
}
}
});
// 20. Groups schema
schemaMap.set('groups', {
table: 'groups',
columns: ['id', 'project_id', 'name', 'description', 'type', 'policy', 'traffic_allocation', 'archived', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['data_json'],
joins: {
projects: {
table: 'projects',
on: 'groups.project_id = projects.id',
type: 'INNER'
}
}
});
// 21. Extensions schema
schemaMap.set('extensions', {
table: 'extensions',
columns: ['id', 'project_id', 'name', 'description', 'extension_type', 'implementation', 'enabled', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['implementation', 'data_json'],
joins: {
projects: {
table: 'projects',
on: 'extensions.project_id = projects.id',
type: 'INNER'
}
}
});
// 22. Webhooks schema
schemaMap.set('webhooks', {
table: 'webhooks',
columns: ['id', 'project_id', 'name', 'url', 'event_types', 'enabled', 'headers', 'secret', 'created_time', 'updated_time', 'data_json'],
jsonColumns: ['event_types', 'headers', 'data_json'],
joins: {
projects: {
table: 'projects',
on: 'webhooks.project_id = projects.id',
type: 'INNER'
}
}
});
// 23. List Attributes schema
schemaMap.set('list_attributes', {
table: 'list_attributes',
columns: ['id', 'project_id', 'name', 'description', 'key_field', 'list_type', 'list_content', 'created_time', 'updated_time', 'archived', 'data_json'],
jsonColumns: ['list_content', 'data_json'],
joins: {
projects: {
table: 'projects',
on: 'list_attributes.project_id = projects.id',
type: 'INNER'
}
}
});
// 24. Change History schema
schemaMap.set('change_history', {
table: 'change_history',
columns: ['id', 'project_id', 'entity_type', 'entity_id', 'entity_name', 'action', 'timestamp', 'changed_by', 'change_summary', 'archived'],
jsonColumns: [],
joins: {
projects: {
table: 'projects',
on: 'change_history.project_id = projects.id',
type: 'INNER'
}
}
});
// 25. Sync State schema
schemaMap.set('sync_state', {
table: 'sync_state',
columns: ['project_id', 'last_sync_time', 'last_successful_sync', 'sync_in_progress', 'error_count', 'last_error'],
jsonColumns: [],
joins: {
projects: {
table: 'projects',
on: 'sync_state.project_id = projects.id',
type: 'INNER'
}
}
});
// 26. Sync Metadata schema
schemaMap.set('sync_metadata', {
table: 'sync_metadata',
columns: ['key', 'value', 'updated_at'],
jsonColumns: [],
joins: {} // No direct joins
});
getLogger().info({
totalSchemas: schemaMap.size,
implementation: 'COMPLETE',
coverage: '100%',
missingSchemas: 0
}, 'IntelligentFieldMapper schema initialization COMPLETE');
return schemaMap;
}
/**
* Get list of all available entities
*/
getAvailableEntities() {
return Array.from(this.schemaMap.keys()).sort();
}
/**
* Get schema info for an entity
*/
getEntitySchema(entity) {
return this.schemaMap.get(entity);
}
/**
* Validate that all required schemas are present
*/
validateCompleteness() {
const requiredEntities = [
'projects', 'environments', 'flags', 'flag_environments', 'variations',
'variable_definitions', 'rulesets', 'rules', 'changes', 'reports',
'features', 'experiments', 'experiment_results', 'campaigns', 'pages',
'audiences', 'attributes', 'events', 'collaborators', 'groups',
'extensions', 'webhooks', 'list_attributes', 'change_history',
'sync_state', 'sync_metadata'
];
const missing = requiredEntities.filter(entity => !this.schemaMap.has(entity));
return {
isComplete: missing.length === 0,
missing
};
}
/**
* Legacy method for HybridQueryBuilder compatibility
* Resolves a field reference to SQL mapping
*/
resolveField(entityType, fieldPath) {
const result = this.mapField(entityType, fieldPath);
if (result.success) {
return {
sqlField: result.isJsonPath
? `JSON_EXTRACT(${result.table}.${result.column}, '${result.jsonPath}')`
: `${result.table}.${result.column}`,
requiresJsonProcessing: result.isJsonPath
};
}
else {
throw new Error(result.error || `Field ${fieldPath} not found in ${entityType}`);
}
}
/**
* Legacy method for HybridQueryBuilder compatibility
* Gets required joins for an entity
*/
getRequiredJoins(entityType, fields) {
const schema = this.schemaMap.get(entityType);
if (!schema || !schema.joins) {
return [];
}
const joins = [];
// For now, return all available joins for the entity
// In a more sophisticated implementation, this would analyze which fields actually need which joins
for (const [joinEntity, joinInfo] of Object.entries(schema.joins)) {
joins.push({
table: joinInfo.table,
condition: joinInfo.on,
type: joinInfo.type
});
}
return joins;
}
}
//# sourceMappingURL=IntelligentFieldMapper.js.map