@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
947 lines • 37 kB
JavaScript
/**
* UnifiedFieldResolver - Consolidates all field mapping knowledge for dynamic query resolution
*
* This class represents the core of Phase 3 Intelligent Flattening Architecture.
* It consolidates knowledge from:
* - IntelligentPayloadParser (3,750+ field patterns)
* - ComprehensiveAutoCorrector (platform-specific corrections)
* - FieldMapper (entity-specific mappings)
* - FIELDS.generated (complete schema definitions)
*
* The goal is to handle ANY field variation dynamically without hard-coding.
*/
import { IntelligentPayloadParser } from '../parsers/IntelligentPayloadParser.js';
import { ComprehensiveAutoCorrector } from '../validation/ComprehensiveAutoCorrector.js';
import { FieldMapper } from '../parsers/FieldMapper.js';
import { FIELDS } from '../generated/fields.generated.js';
import { getLogger } from '../logging/Logger.js';
/**
* UnifiedFieldResolver - The core field resolution engine
*
* This class implements the 5-step resolution process:
* 1. Normalize field name variations (camelCase, snake_case, etc.)
* 2. Apply synonym mappings (traffic → weight, etc.)
* 3. Apply platform-specific corrections
* 4. Validate against schema
* 5. Determine optimal access strategy
*/
export class UnifiedFieldResolver {
payloadParser;
fieldMapper;
fieldsSchema;
logger = getLogger();
constructor() {
this.logger.debug('UnifiedFieldResolver: Initializing field mapping systems...');
this.initializeMappers();
this.logger.debug('UnifiedFieldResolver: Initialization complete');
}
/**
* Resolve a single field reference to database access strategy
*
* @param input - Field name as provided by user/agent
* @param context - Query context for resolution
* @returns Complete field resolution result
*/
resolveField(input, context) {
this.logger.debug(`UnifiedFieldResolver: Resolving field "${input}" for entity "${context.primaryEntity}"`);
try {
// Validate inputs
if (!input || typeof input !== 'string') {
throw new Error('Field name must be a non-empty string');
}
if (!context || !context.primaryEntity) {
throw new Error('Query context with primaryEntity is required');
}
// Execute the 5-step resolution process
const step1 = this.normalizeFieldName(input, context);
this.logger.debug(`Step 1 - Normalized: "${input}" → "${step1.normalized}"`);
const step2 = this.applySynonymMappings(step1.normalized, context);
this.logger.debug(`Step 2 - Synonym mapping: "${step1.normalized}" → "${step2.mapped}"`);
const step3 = this.applyPlatformCorrections(step2.mapped, context);
this.logger.debug(`Step 3 - Platform correction: "${step2.mapped}" → "${step3.corrected}"`);
const step4 = this.validateAgainstSchema(step3.corrected, context);
this.logger.debug(`Step 4 - Schema validation: ${step4.isValid ? 'PASSED' : 'FAILED'}`);
if (!step4.isValid) {
throw new Error(`Field validation failed: ${step4.errors.join(', ')}`);
}
const step5 = this.determineAccessStrategy(step4, context);
this.logger.debug(`Step 5 - Access strategy: ${step5.requiresFlattening ? 'FLATTENING' : 'DIRECT'}`);
// Preserve original input
step5.originalInput = input;
return step5;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.logger.error(`UnifiedFieldResolver: Failed to resolve field "${input}": ${errorMessage}`);
throw error;
}
}
/**
* Resolve multiple fields in batch for optimal performance
*
* @param inputs - Array of field names
* @param context - Query context for resolution
* @returns Batch resolution result
*/
resolveFields(inputs, context) {
this.logger.debug(`UnifiedFieldResolver: Resolving ${inputs.length} fields in batch`);
const resolvedFields = [];
const unresolvedFields = [];
const allJoins = new Set();
const allConditions = new Set();
let requiresFlattening = false;
let requiresJsonProcessing = false;
// Resolve each field individually
for (const input of inputs) {
try {
const resolved = this.resolveField(input, context);
resolvedFields.push(resolved);
// Collect JOINs and conditions
resolved.requiredJoins?.forEach(join => allJoins.add(join));
resolved.additionalConditions?.forEach(condition => allConditions.add(condition));
// Track complexity flags
if (resolved.requiresFlattening)
requiresFlattening = true;
if (resolved.jsonataExpression)
requiresJsonProcessing = true;
}
catch (error) {
this.logger.warn(`Failed to resolve field "${input}": ${error}`);
unresolvedFields.push(input);
}
}
// Generate optimization hints
const optimizationHints = this.getOptimizationHints(resolvedFields);
return {
resolvedFields,
unresolvedFields,
requiredJoins: Array.from(allJoins),
additionalConditions: Array.from(allConditions),
requiresFlattening,
requiresJsonProcessing,
optimizationHints
};
}
/**
* Step 1: Normalize field name variations
* Handles: camelCase ↔ snake_case ↔ PascalCase ↔ kebab-case
*/
normalizeFieldName(input, context) {
const transformations = [];
let normalized = input.trim();
// Track original case for confidence scoring
const originalCase = this.detectCaseType(input);
transformations.push(`detected_case:${originalCase}`);
// Handle camelCase/PascalCase: insert _ before uppercase letters
if (/[a-z][A-Z]/.test(normalized)) {
normalized = normalized.replace(/([a-z])([A-Z])/g, '$1_$2');
transformations.push('camelCase_to_snake_case');
}
// Convert kebab-case to snake_case
if (normalized.includes('-')) {
normalized = normalized.replace(/-/g, '_');
transformations.push('kebab_case_to_snake_case');
}
// Handle multiple underscores
if (normalized.includes('__')) {
normalized = normalized.replace(/_+/g, '_');
transformations.push('collapsed_multiple_underscores');
}
// Convert to lowercase
if (normalized !== normalized.toLowerCase()) {
normalized = normalized.toLowerCase();
transformations.push('converted_to_lowercase');
}
// Remove leading/trailing underscores
const beforeTrim = normalized;
normalized = normalized.replace(/^_+|_+$/g, '');
if (normalized !== beforeTrim) {
transformations.push('removed_leading_trailing_underscores');
}
// Calculate confidence based on transformations needed
const confidence = this.calculateNormalizationConfidence(input, normalized, transformations);
return {
normalized,
transformations,
confidence
};
}
/**
* Detect the case type of input field
*/
detectCaseType(input) {
if (/^[a-z]+(_[a-z]+)*$/.test(input))
return 'snake_case';
if (/^[a-z]+([A-Z][a-z]*)*$/.test(input))
return 'camelCase';
if (/^[A-Z][a-z]*([A-Z][a-z]*)*$/.test(input))
return 'PascalCase';
if (/^[a-z]+(-[a-z]+)*$/.test(input))
return 'kebab-case';
if (/^[A-Z_]+$/.test(input))
return 'UPPER_CASE';
return 'mixed_case';
}
/**
* Calculate confidence score for normalization
*/
calculateNormalizationConfidence(original, normalized, transformations) {
// Base confidence
let confidence = 1.0;
// Reduce confidence for each transformation
confidence -= transformations.length * 0.05;
// Boost confidence for common patterns
if (transformations.includes('camelCase_to_snake_case'))
confidence += 0.1;
if (transformations.includes('kebab_case_to_snake_case'))
confidence += 0.1;
// Penalize unusual patterns
if (transformations.includes('collapsed_multiple_underscores'))
confidence -= 0.2;
if (original.length < 2)
confidence -= 0.3;
return Math.max(0.1, Math.min(1.0, confidence));
}
/**
* Step 2: Apply synonym mappings
* Handles: traffic → weight, traffic_allocation → percentage_included, etc.
*/
applySynonymMappings(normalized, context) {
const synonyms = [];
let mapped = normalized;
let contextInfo = `entity:${context.primaryEntity}`;
// Critical synonyms (highest priority) - these solve the original error
const criticalSynonyms = this.getCriticalSynonyms();
if (criticalSynonyms[mapped]) {
synonyms.push({ from: mapped, to: criticalSynonyms[mapped] });
mapped = criticalSynonyms[mapped];
contextInfo += ',critical_mapping';
}
// Entity-specific synonyms
const entitySynonyms = this.getEntitySpecificSynonyms(context.primaryEntity);
if (entitySynonyms[mapped]) {
synonyms.push({ from: mapped, to: entitySynonyms[mapped] });
mapped = entitySynonyms[mapped];
contextInfo += ',entity_specific';
}
// Platform-specific synonyms
if (context.platform) {
const platformSynonyms = this.getPlatformSpecificSynonyms(context.platform);
if (platformSynonyms[mapped]) {
synonyms.push({ from: mapped, to: platformSynonyms[mapped] });
mapped = platformSynonyms[mapped];
contextInfo += `,platform:${context.platform}`;
}
}
// Common field synonyms (lowest priority)
const commonSynonyms = this.getCommonSynonyms();
if (commonSynonyms[mapped]) {
synonyms.push({ from: mapped, to: commonSynonyms[mapped] });
mapped = commonSynonyms[mapped];
contextInfo += ',common_mapping';
}
return {
mapped,
synonyms,
context: contextInfo
};
}
/**
* Get critical synonyms that solve the "variations.key does not exist" error
*/
getCriticalSynonyms() {
return {
// Traffic/Weight synonyms (most critical)
'traffic': 'weight',
'traffic_allocation': 'percentage_included',
'traffic_alloc': 'percentage_included',
// Key field synonyms
'flag_key': 'key',
'experiment_key': 'key',
'audience_key': 'key',
'variation_key': 'key',
'rule_key': 'key',
'event_key': 'key',
// Status/State synonyms
'is_active': 'enabled',
'is_archived': 'archived',
'state': 'status',
'is_enabled': 'enabled'
};
}
/**
* Get entity-specific synonym mappings
*/
getEntitySpecificSynonyms(entity) {
const mappings = {
'flags': {
'flag_name': 'name',
'flag_desc': 'description',
'flag_status': 'status'
},
'flag': {
'flag_name': 'name',
'flag_desc': 'description',
'flag_status': 'status'
},
'experiments': {
'exp_name': 'name',
'exp_key': 'key',
'experiment_status': 'status'
},
'experiment': {
'exp_name': 'name',
'exp_key': 'key',
'experiment_status': 'status'
},
'variations': {
'var_key': 'key',
'variation_name': 'name',
'var_weight': 'weight'
},
'variation': {
'var_key': 'key',
'variation_name': 'name',
'var_weight': 'weight'
},
'audiences': {
'aud_key': 'key',
'audience_name': 'name'
},
'audience': {
'aud_key': 'key',
'audience_name': 'name'
},
'rules': {
'rule_enabled': 'enabled',
'rule_active': 'enabled'
},
'rule': {
'rule_enabled': 'enabled',
'rule_active': 'enabled'
},
'events': {
'event_name': 'name',
'event_type': 'category'
},
'event': {
'event_name': 'name',
'event_type': 'category'
},
'changes': {
'change_type': 'type',
'change_selector': 'selector'
}
};
return mappings[entity] || {};
}
/**
* Get platform-specific synonym mappings
*/
getPlatformSpecificSynonyms(platform) {
const mappings = {
'feature': {
'rollout_percentage': 'traffic_allocation',
'flag_variations': 'variations',
'feature_enabled': 'enabled',
'variable_map': 'variables',
'default_value': 'default_variation_key'
},
'web': {
'holdback_percentage': 'holdback',
'page_targets': 'page_ids',
'url_match': 'url_targeting',
'experiment_changes': 'changes',
'audience_targeting': 'audience_conditions'
},
'custom': {}
};
return mappings[platform] || {};
}
/**
* Get common field synonyms (lowest priority)
*/
getCommonSynonyms() {
return {
// Description variants
'desc': 'description',
'summary': 'description',
'details': 'description',
// Name variants
'title': 'name',
'label': 'name',
'display_name': 'name',
// ID variants
'identifier': 'id',
'uuid': 'id',
'guid': 'id',
// Time variants
'created': 'created_time',
'updated': 'updated_time',
'modified': 'last_modified',
'timestamp': 'created_time',
// Boolean variants
'active': 'enabled',
'inactive': 'disabled',
'deleted': 'archived'
};
}
/**
* Step 3: Apply platform-specific corrections
* Handles differences between Web and Feature Experimentation
* Uses ComprehensiveAutoCorrector.autoCorrect() static method
*/
applyPlatformCorrections(mapped, context) {
const corrections = [];
let corrected = mapped;
const platform = context.platform || 'custom';
try {
// Create a mock entity data object for the auto-corrector
const mockEntityData = {
[mapped]: 'test_value', // The corrector needs some data to work with
entity_type: context.primaryEntity,
platform: platform
};
// Use ComprehensiveAutoCorrector to apply platform-specific corrections
const correctionResult = ComprehensiveAutoCorrector.autoCorrect(context.primaryEntity, mockEntityData, {
platform: platform
});
// Check if any corrections were applied to our field
if (correctionResult.correctedData && correctionResult.correctedData[mapped] !== mockEntityData[mapped]) {
corrections.push({
type: 'auto_corrector',
description: `Applied ComprehensiveAutoCorrector platform-specific correction`
});
}
// Apply manual platform-specific field corrections
const manualCorrections = this.getManualPlatformCorrections(mapped, platform, context.primaryEntity);
if (manualCorrections.correctedField !== mapped) {
corrected = manualCorrections.correctedField;
corrections.push(...manualCorrections.corrections);
}
}
catch (error) {
// If auto-corrector fails, continue with manual corrections only
this.logger.warn(`ComprehensiveAutoCorrector failed for field "${mapped}": ${error}`);
const manualCorrections = this.getManualPlatformCorrections(mapped, platform, context.primaryEntity);
corrected = manualCorrections.correctedField;
corrections.push(...manualCorrections.corrections);
}
return {
corrected,
corrections,
platform
};
}
/**
* Apply manual platform-specific field corrections
*/
getManualPlatformCorrections(field, platform, entity) {
const corrections = [];
let correctedField = field;
// Platform-specific field availability checks
const platformExclusiveFields = this.getPlatformExclusiveFields();
// Feature Experimentation exclusive fields
if (platform === 'web' && platformExclusiveFields.featureOnly.includes(field)) {
corrections.push({
type: 'platform_availability',
description: `Field "${field}" is only available in Feature Experimentation, not Web Experimentation`
});
}
// Web Experimentation exclusive fields
if (platform === 'feature' && platformExclusiveFields.webOnly.includes(field)) {
corrections.push({
type: 'platform_availability',
description: `Field "${field}" is only available in Web Experimentation, not Feature Experimentation`
});
}
// Platform-specific field name corrections
const fieldCorrections = this.getPlatformFieldCorrections();
const platformKey = `${platform}_${entity}`;
if (fieldCorrections[platformKey] && fieldCorrections[platformKey][field]) {
const newField = fieldCorrections[platformKey][field];
corrections.push({
type: 'platform_field_correction',
description: `Platform-specific correction: "${field}" → "${newField}" for ${platform} ${entity}`
});
correctedField = newField;
}
return { correctedField, corrections };
}
/**
* Get platform-exclusive field lists
*/
getPlatformExclusiveFields() {
return {
featureOnly: [
'traffic_allocation',
'variable_definitions',
'rollout_rules',
'default_variation_key',
'variables',
'rollout_id',
'sdk_key',
'datafile_url'
],
webOnly: [
'holdback',
'changes',
'url_targeting',
'page_ids',
'activation_mode',
'custom_css',
'custom_js',
'redirect_url'
],
common: [
'id', 'key', 'name', 'description',
'status', 'archived', 'enabled',
'created_time', 'updated_time', 'last_modified',
'project_id', 'account_id',
'audience_conditions', 'audience_ids',
'metrics', 'primary_metric',
'confidence_threshold'
]
};
}
/**
* Get platform-specific field name corrections
*/
getPlatformFieldCorrections() {
return {
// Feature Experimentation corrections
'feature_flags': {
'variations': 'variations', // No correction needed - exists as separate table
'rollout': 'traffic_allocation'
},
'feature_experiments': {
'holdback': 'traffic_allocation', // Web field mapped to Feature equivalent
'changes': 'variable_definitions'
},
// Web Experimentation corrections
'web_experiments': {
'traffic_allocation': 'holdback', // Feature field mapped to Web equivalent
'variable_definitions': 'changes'
},
'web_flags': {
'rollout_rules': 'url_targeting'
}
};
}
/**
* Step 4: Validate against schema
* Ensures the field exists and gets type information
*/
validateAgainstSchema(corrected, context) {
const errors = [];
const suggestions = [];
let isValid = false;
let fieldInfo = {
name: corrected,
path: corrected,
type: 'any',
isArray: false,
isNested: false
};
try {
// Handle nested field paths (e.g., "variations.key")
const pathParts = corrected.split('.');
const rootField = pathParts[0];
const nestedPath = pathParts.slice(1).join('.');
// Get entity schema from FIELDS
const entitySchema = this.getEntitySchema(context.primaryEntity);
if (!entitySchema) {
errors.push(`No schema found for entity type: ${context.primaryEntity}`);
suggestions.push(`Available entities: ${Object.keys(this.fieldsSchema).join(', ')}`);
return { field: fieldInfo, isValid: false, errors, suggestions };
}
// Check if root field exists in schema
if (this.isFieldInSchema(rootField, entitySchema)) {
isValid = true;
fieldInfo.type = this.getFieldType(rootField, entitySchema);
fieldInfo.isArray = this.isArrayField(rootField, entitySchema);
// Handle nested paths
if (nestedPath) {
fieldInfo.isNested = true;
fieldInfo.path = corrected;
// For array fields with nested access, this requires flattening
if (fieldInfo.isArray) {
fieldInfo.type = this.getNestedFieldType(nestedPath, rootField, entitySchema);
}
}
}
else {
// Field not found - try fuzzy matching for suggestions
isValid = false;
errors.push(`Field "${rootField}" not found in ${context.primaryEntity} schema`);
const fuzzyMatches = this.findFuzzyMatches(rootField, entitySchema);
if (fuzzyMatches.length > 0) {
suggestions.push(`Did you mean: ${fuzzyMatches.join(', ')}?`);
}
// Suggest available fields
const availableFields = this.getAvailableFields(entitySchema);
suggestions.push(`Available fields: ${availableFields.slice(0, 10).join(', ')}${availableFields.length > 10 ? '...' : ''}`);
}
}
catch (error) {
errors.push(`Schema validation error: ${error}`);
isValid = false;
}
return {
field: fieldInfo,
isValid,
errors,
suggestions
};
}
/**
* Get entity schema from FIELDS
*/
getEntitySchema(entity) {
// Normalize entity name for schema lookup
const normalizedEntity = this.normalizeEntityForSchema(entity);
return this.fieldsSchema[normalizedEntity];
}
/**
* Normalize entity type for schema lookup
*/
normalizeEntityForSchema(entity) {
// Convert plural to singular and handle variations
const entityMappings = {
'flags': 'flag',
'flag': 'flag',
'experiments': 'experiment',
'experiment': 'experiment',
'variations': 'variation',
'variation': 'variation',
'audiences': 'audience',
'audience': 'audience',
'rules': 'rule',
'rule': 'rule',
'events': 'event',
'event': 'event',
'attributes': 'attribute',
'attribute': 'attribute',
'pages': 'page',
'page': 'page',
'projects': 'project',
'project': 'project',
'environments': 'environment',
'environment': 'environment',
'rulesets': 'ruleset',
'ruleset': 'ruleset',
'campaigns': 'campaign',
'campaign': 'campaign',
'changes': 'change'
};
return entityMappings[entity] || entity;
}
/**
* Check if field exists in entity schema
*/
isFieldInSchema(field, schema) {
if (!schema)
return false;
// Check required and optional fields
const requiredFields = schema.required || [];
const optionalFields = schema.optional || [];
return requiredFields.includes(field) || optionalFields.includes(field);
}
/**
* Get field type from schema
*/
getFieldType(field, schema) {
if (!schema || !schema.fieldTypes)
return 'any';
return schema.fieldTypes[field] || 'any';
}
/**
* Check if field is an array type
*/
isArrayField(field, schema) {
const fieldType = this.getFieldType(field, schema);
return fieldType === 'array';
}
/**
* Get nested field type for array elements
*/
getNestedFieldType(nestedPath, rootField, schema) {
// For nested paths in arrays, try to infer the type
const commonNestedTypes = {
'key': 'string',
'name': 'string',
'id': 'number',
'weight': 'number',
'percentage_included': 'number',
'enabled': 'boolean',
'status': 'string',
'type': 'string'
};
const leafField = nestedPath.split('.').pop() || nestedPath;
return commonNestedTypes[leafField] || 'any';
}
/**
* Find fuzzy matches for field name
*/
findFuzzyMatches(field, schema) {
if (!schema)
return [];
const allFields = [
...(schema.required || []),
...(schema.optional || [])
];
const matches = [];
const fieldLower = field.toLowerCase();
for (const availableField of allFields) {
const availableLower = availableField.toLowerCase();
// Exact substring match
if (availableLower.includes(fieldLower) || fieldLower.includes(availableLower)) {
matches.push(availableField);
continue;
}
// Levenshtein distance for typos
if (this.calculateLevenshteinDistance(fieldLower, availableLower) <= 2) {
matches.push(availableField);
}
}
return matches.slice(0, 5); // Limit suggestions
}
/**
* Calculate Levenshtein distance for fuzzy matching
*/
calculateLevenshteinDistance(str1, str2) {
const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
for (let i = 0; i <= str1.length; i++)
matrix[0][i] = i;
for (let j = 0; j <= str2.length; j++)
matrix[j][0] = j;
for (let j = 1; j <= str2.length; j++) {
for (let i = 1; i <= str1.length; i++) {
const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
matrix[j][i] = Math.min(matrix[j][i - 1] + 1, // deletion
matrix[j - 1][i] + 1, // insertion
matrix[j - 1][i - 1] + indicator // substitution
);
}
}
return matrix[str2.length][str1.length];
}
/**
* Get all available fields from schema
*/
getAvailableFields(schema) {
if (!schema)
return [];
return [
...(schema.required || []),
...(schema.optional || [])
].sort();
}
/**
* Step 5: Determine optimal access strategy
* Decides between direct SQL, JSON_EXTRACT, or flattening
*/
determineAccessStrategy(validated, context) {
const field = validated.field;
const requiresFlattening = this.requiresFlattening(validated, context);
const sqlAccessor = this.buildSQLAccessor(validated);
const jsonataExpression = this.buildJsonataExpression(validated, context);
// Determine required JOINs
const requiredJoins = this.determineRequiredJoins(field, context);
const additionalConditions = this.determineAdditionalConditions(field, context);
return {
originalInput: field.name, // Will be overridden by caller with actual input
canonicalName: field.name,
accessPath: field.path,
dataType: this.normalizeDataType(field.type),
isArray: field.isArray,
isNested: field.isNested,
requiresFlattening,
sqlAccessor,
jsonataExpression,
requiredJoins,
additionalConditions
};
}
/**
* Determine if field requires array flattening
*/
requiresFlattening(validated, context) {
const field = validated.field;
// If explicitly requesting individual elements from arrays
if (context.needsIndividualElements && field.isArray) {
return true;
}
// Nested field access on arrays requires flattening
if (field.isNested && field.isArray) {
return true;
}
// Known array fields that require flattening
const arrayFieldsRequiringFlattening = [
'variations', 'metrics', 'rules', 'changes', 'audience_ids', 'page_ids'
];
const rootField = field.path.split('.')[0];
if (arrayFieldsRequiringFlattening.includes(rootField) && field.path.includes('.')) {
return true;
}
return false;
}
/**
* Build SQL accessor expression
*/
buildSQLAccessor(validated) {
const field = validated.field;
// Simple field access
if (!field.isNested) {
return field.name;
}
// JSON extraction for nested fields
const pathParts = field.path.split('.');
const rootField = pathParts[0];
const jsonPath = pathParts.slice(1).join('.');
// Different JSON extraction patterns based on field structure
if (field.isArray) {
// Array field access - requires JSON_EXTRACT with array notation
return `JSON_EXTRACT(${rootField}, '$[*].${jsonPath}')`;
}
else {
// Object field access - simple JSON_EXTRACT
return `JSON_EXTRACT(${rootField}, '$.${jsonPath}')`;
}
}
/**
* Build JSONata expression for complex processing
*/
buildJsonataExpression(validated, context) {
const field = validated.field;
// Only generate JSONata for complex nested array access
if (!field.isNested || !field.isArray) {
return undefined;
}
const pathParts = field.path.split('.');
const rootField = pathParts[0];
const nestedPath = pathParts.slice(1).join('.');
// Generate JSONata expression for array flattening
return `$.${rootField}[*].${nestedPath}`;
}
/**
* Determine required database JOINs
*/
determineRequiredJoins(field, context) {
const joins = [];
// Known JOIN patterns for specific field types
const joinPatterns = {
// Variations access from experiments/flags
'variations': `LEFT JOIN variations ON ${context.primaryEntity}.id = variations.${context.primaryEntity.slice(0, -1)}_id`,
// Rules access from flags
'rules': `LEFT JOIN rules ON flags.id = rules.flag_id`,
// Environment-specific access
'environments': `LEFT JOIN flag_environments ON flags.id = flag_environments.flag_id`,
// Metrics access
'metrics': `LEFT JOIN experiment_metrics ON experiments.id = experiment_metrics.experiment_id`
};
const rootField = field.path.split('.')[0];
if (joinPatterns[rootField]) {
joins.push(joinPatterns[rootField]);
}
return joins;
}
/**
* Determine additional WHERE conditions
*/
determineAdditionalConditions(field, context) {
const conditions = [];
// Platform-specific conditions
if (context.platform) {
if (context.platform === 'feature') {
conditions.push("platform = 'feature'");
}
else if (context.platform === 'web') {
conditions.push("platform = 'web'");
}
}
// Project-specific conditions
if (context.projectId) {
conditions.push(`project_id = '${context.projectId}'`);
}
return conditions;
}
/**
* Normalize data type names
*/
normalizeDataType(type) {
const typeMap = {
'string': 'string',
'text': 'string',
'varchar': 'string',
'integer': 'number',
'number': 'number',
'float': 'number',
'double': 'number',
'boolean': 'boolean',
'bool': 'boolean',
'array': 'array',
'object': 'object',
'json': 'object'
};
return typeMap[type.toLowerCase()] || 'any';
}
// Legacy methods are now integrated into determineAccessStrategy - these were placeholder signatures
/**
* Initialize all field mapping systems
* This method integrates existing infrastructure without modifying it
*/
initializeMappers() {
try {
// Initialize IntelligentPayloadParser
this.payloadParser = new IntelligentPayloadParser();
this.logger.debug('UnifiedFieldResolver: IntelligentPayloadParser initialized');
// Initialize FieldMapper
this.fieldMapper = new FieldMapper();
this.logger.debug('UnifiedFieldResolver: FieldMapper initialized');
// Note: ComprehensiveAutoCorrector is a static class - no instantiation needed
this.logger.debug('UnifiedFieldResolver: ComprehensiveAutoCorrector available as static class');
// Reference the schema directly
this.fieldsSchema = FIELDS;
this.logger.debug('UnifiedFieldResolver: FIELDS schema loaded');
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.logger.error('UnifiedFieldResolver: Failed to initialize mappers', errorMessage);
throw new Error(`Failed to initialize field mapping systems: ${errorMessage}`);
}
}
/**
* Get performance optimization hints for query planning
*/
getOptimizationHints(resolvedFields) {
const hasComplexFields = resolvedFields.some(f => f.requiresFlattening || f.jsonataExpression);
const hasJoins = resolvedFields.some(f => f.requiredJoins && f.requiredJoins.length > 0);
const hasNestedAccess = resolvedFields.some(f => f.isNested);
// Determine complexity level
let complexityLevel = 'simple';
if (hasComplexFields) {
complexityLevel = 'complex';
}
else if (hasNestedAccess || hasJoins) {
complexityLevel = 'moderate';
}
// Can use simple extract if no flattening needed
const canUseSimpleExtract = !resolvedFields.some(f => f.requiresFlattening);
// Estimate result size impact
let resultSizeImpact = 'minimal';
const flatteningFields = resolvedFields.filter(f => f.requiresFlattening).length;
if (flatteningFields > 2) {
resultSizeImpact = 'significant';
}
else if (flatteningFields > 0 || hasJoins) {
resultSizeImpact = 'moderate';
}
return {
canUseSimpleExtract,
complexityLevel,
resultSizeImpact
};
}
}
//# sourceMappingURL=UnifiedFieldResolver.js.map