UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

421 lines 19.7 kB
import { getLogger } from '../logging/Logger.js'; /** * Centralized reference resolver for all entity types in template mode. * Handles conversion of reference objects like {ref: {name: "Entity Name"}} to actual IDs. * * Usage: * const resolver = new ReferenceResolver(entityRouter); * const resolved = await resolver.resolveEntityReferences(templateData, entityType, projectId); * * Supported reference patterns: * - {ref: {id: "123"}} - Direct ID reference * - {ref: {name: "Entity Name"}} - Name-based reference (converted to ID) * - {ref: {key: "entity_key"}} - Key-based reference (converted to ID) * - Direct values: "123" or 123 - Treated as direct ID references */ export class ReferenceResolver { entityRouter; constructor(entityRouter) { this.entityRouter = entityRouter; } /** * PUBLIC API: Resolves event references specifically * @param templateData Raw template data containing event reference objects * @param projectId Project ID for scoped searches * @returns Object containing resolved event reference fields */ async resolveEventReferences(templateData, projectId) { const resolved = {}; await this.resolveEventReferencesInternal(templateData, 'event', resolved, projectId); return resolved; } /** * PUBLIC API: Resolves attribute references specifically * @param templateData Raw template data containing attribute reference objects * @param projectId Project ID for scoped searches * @returns Object containing resolved attribute reference fields */ async resolveAttributeReferences(templateData, projectId) { const resolved = {}; await this.resolveAttributeReferencesInternal(templateData, 'attribute', resolved, projectId); return resolved; } /** * PUBLIC API: Resolves extension references specifically * @param templateData Raw template data containing extension reference objects * @param projectId Project ID for scoped searches * @returns Object containing resolved extension reference fields */ async resolveExtensionReferences(templateData, projectId) { const resolved = {}; await this.resolveExtensionReferencesInternal(templateData, 'extension', resolved, projectId); return resolved; } /** * PUBLIC API: Resolves variable references specifically * @param templateData Raw template data containing variable reference objects * @param projectId Project ID for scoped searches * @returns Object containing resolved variable reference fields */ async resolveVariableReferences(templateData, projectId) { const resolved = {}; await this.resolveVariableReferencesInternal(templateData, 'variable', resolved, projectId); return resolved; } /** * PUBLIC API: Resolves environment references specifically * @param templateData Raw template data containing environment reference objects * @param projectId Project ID for scoped searches * @returns Object containing resolved environment reference fields */ async resolveEnvironmentReferences(templateData, projectId) { const resolved = {}; await this.resolveEnvironmentReferencesInternal(templateData, 'environment', resolved, projectId); return resolved; } /** * PUBLIC API: Resolves group references specifically * @param templateData Raw template data containing group reference objects * @param projectId Project ID for scoped searches * @returns Object containing resolved group reference fields */ async resolveGroupReferences(templateData, projectId) { const resolved = {}; await this.resolveGroupReferencesInternal(templateData, 'group', resolved, projectId); return resolved; } /** * PUBLIC API: Resolves page references specifically * @param templateData Raw template data containing page reference objects * @param projectId Project ID for scoped searches * @returns Object containing resolved page reference fields */ async resolvePageReferences(templateData, projectId) { const resolved = {}; await this.resolvePageReferencesInternal(templateData, 'page', resolved, projectId); return resolved; } /** * PUBLIC API: Resolves audience references specifically * @param templateData Raw template data containing audience reference objects * @param projectId Project ID for scoped searches * @returns Object containing resolved audience reference fields */ async resolveAudienceReferences(templateData, projectId) { const resolved = {}; await this.resolveAudienceReferencesInternal(templateData, 'audience', resolved, projectId); return resolved; } /** * Resolves all entity references in template data for a specific entity type * @param templateData Raw template data containing reference objects * @param entityType The type of entity being created (for context/logging) * @param projectId Project ID for scoped searches * @returns Object containing resolved reference fields to merge into processed template */ async resolveEntityReferences(templateData, entityType, projectId) { const resolved = {}; getLogger().info({ method: 'resolveEntityReferences', entityType, projectId, templateDataKeys: Object.keys(templateData), stage: 'start' }, 'Starting centralized reference resolution'); // Page references (campaigns and experiments) await this.resolvePageReferencesInternal(templateData, entityType, resolved, projectId); // Audience references (experiments and campaigns) await this.resolveAudienceReferencesInternal(templateData, entityType, resolved, projectId); // Event references (metrics, goals) await this.resolveEventReferencesInternal(templateData, entityType, resolved, projectId); // Attribute references (audiences, targeting) await this.resolveAttributeReferencesInternal(templateData, entityType, resolved, projectId); // Extension references (integrations) await this.resolveExtensionReferencesInternal(templateData, entityType, resolved, projectId); // Variable references (feature flags) await this.resolveVariableReferencesInternal(templateData, entityType, resolved, projectId); // Environment references (deployments, rollouts) await this.resolveEnvironmentReferencesInternal(templateData, entityType, resolved, projectId); // Group references (audience segments) await this.resolveGroupReferencesInternal(templateData, entityType, resolved, projectId); getLogger().info({ method: 'resolveEntityReferences', entityType, projectId, resolvedFields: Object.keys(resolved), stage: 'complete' }, 'Completed centralized reference resolution'); return resolved; } /** * Resolves page references for campaigns and experiments * Handles both pages (array) and page (singular) patterns */ async resolvePageReferencesInternal(templateData, entityType, resolved, projectId) { // Handle pages array (campaigns and some experiments) if (templateData.pages && Array.isArray(templateData.pages)) { getLogger().info({ method: 'resolvePageReferences', entityType, arrayPattern: true, pagesCount: templateData.pages.length, stage: 'pages_array_start' }, 'Resolving page references (array pattern)'); const resolvedPageIds = await this.resolveReferenceArray(templateData.pages, 'page', projectId); if (resolvedPageIds.length > 0) { resolved.page_ids = resolvedPageIds; // Handle nested structures (orchestration compatibility) if (entityType === 'campaign') { resolved.campaign = resolved.campaign || {}; resolved.campaign.page_ids = resolvedPageIds; } } } // Handle singular page reference (experiments per documentation) if (templateData.page && templateData.page.ref) { getLogger().info({ method: 'resolvePageReferences', entityType, singularPattern: true, pageRef: templateData.page.ref, stage: 'page_singular_start' }, 'Resolving page reference (singular pattern)'); const resolvedPageId = await this.resolveSingleReference(templateData.page, 'page', projectId); if (resolvedPageId) { resolved.page = { id: resolvedPageId }; resolved.page_ids = [resolvedPageId]; } } } /** * Resolves audience references for experiments and campaigns */ async resolveAudienceReferencesInternal(templateData, entityType, resolved, projectId) { if (templateData.audiences && Array.isArray(templateData.audiences)) { getLogger().info({ method: 'resolveAudienceReferences', entityType, audiencesCount: templateData.audiences.length, stage: 'audiences_array_start' }, 'Resolving audience references'); const resolvedAudienceIds = await this.resolveReferenceArray(templateData.audiences, 'audience', projectId); if (resolvedAudienceIds.length > 0) { resolved.audiences = resolvedAudienceIds; // Preserve audience operator if specified if (templateData.audiences_operator) { resolved.audiences_operator = templateData.audiences_operator; } } } } /** * Resolves event references for metrics and goals */ async resolveEventReferencesInternal(templateData, entityType, resolved, projectId) { // Handle events array if (templateData.events && Array.isArray(templateData.events)) { const resolvedEventIds = await this.resolveReferenceArray(templateData.events, 'event', projectId); if (resolvedEventIds.length > 0) { resolved.event_ids = resolvedEventIds; } } // Handle singular event reference if (templateData.event && templateData.event.ref) { const resolvedEventId = await this.resolveSingleReference(templateData.event, 'event', projectId); if (resolvedEventId) { resolved.event_id = resolvedEventId; } } // Handle event references nested inside metrics array (experiments) if (templateData.metrics && Array.isArray(templateData.metrics)) { getLogger().info({ method: 'resolveEventReferences', entityType, metricsPattern: true, metricsCount: templateData.metrics.length, stage: 'metrics_array_start' }, 'Resolving event references inside metrics array'); const resolvedMetrics = []; for (let i = 0; i < templateData.metrics.length; i++) { const metric = templateData.metrics[i]; if (metric.event && metric.event.ref) { const resolvedEventId = await this.resolveSingleReference(metric.event, 'event', projectId); if (resolvedEventId) { // Create resolved metric with event_id instead of event.ref (per swagger spec) const resolvedMetric = { ...metric }; delete resolvedMetric.event; // Remove the entire event object resolvedMetric.event_id = parseInt(resolvedEventId); // Set event_id as integer resolvedMetrics[i] = resolvedMetric; getLogger().info({ method: 'resolveEventReferences', entityType, metricIndex: i, resolvedEventId, stage: 'metric_event_resolved' }, 'Resolved event reference in metric'); } else { resolvedMetrics[i] = metric; // Keep original if resolution failed } } else { resolvedMetrics[i] = metric; // Keep original if no event ref } } if (resolvedMetrics.length > 0) { resolved.metrics = resolvedMetrics; } } } /** * Resolves attribute references for audience conditions */ async resolveAttributeReferencesInternal(templateData, entityType, resolved, projectId) { if (templateData.attributes && Array.isArray(templateData.attributes)) { const resolvedAttributeIds = await this.resolveReferenceArray(templateData.attributes, 'attribute', projectId); if (resolvedAttributeIds.length > 0) { resolved.attribute_ids = resolvedAttributeIds; } } } /** * Resolves extension references for integrations */ async resolveExtensionReferencesInternal(templateData, entityType, resolved, projectId) { if (templateData.extensions && Array.isArray(templateData.extensions)) { const resolvedExtensionIds = await this.resolveReferenceArray(templateData.extensions, 'extension', projectId); if (resolvedExtensionIds.length > 0) { resolved.extension_ids = resolvedExtensionIds; } } } /** * Resolves variable references for feature flags */ async resolveVariableReferencesInternal(templateData, entityType, resolved, projectId) { if (templateData.variables && Array.isArray(templateData.variables)) { const resolvedVariableIds = await this.resolveReferenceArray(templateData.variables, 'variable', projectId); if (resolvedVariableIds.length > 0) { resolved.variable_ids = resolvedVariableIds; } } } /** * Resolves environment references for deployments */ async resolveEnvironmentReferencesInternal(templateData, entityType, resolved, projectId) { if (templateData.environments && Array.isArray(templateData.environments)) { const resolvedEnvironmentIds = await this.resolveReferenceArray(templateData.environments, 'environment', projectId); if (resolvedEnvironmentIds.length > 0) { resolved.environment_ids = resolvedEnvironmentIds; } } } /** * Resolves group references for audience segments */ async resolveGroupReferencesInternal(templateData, entityType, resolved, projectId) { if (templateData.groups && Array.isArray(templateData.groups)) { const resolvedGroupIds = await this.resolveReferenceArray(templateData.groups, 'group', projectId); if (resolvedGroupIds.length > 0) { resolved.group_ids = resolvedGroupIds; } } } /** * Resolves an array of reference objects to IDs * @param refArray Array of reference objects like [{ref: {name: "Name"}}, {ref: {id: "123"}}] * @param entityType Type of entity being referenced * @param projectId Project ID for scoped searches * @returns Array of resolved IDs */ async resolveReferenceArray(refArray, entityType, projectId) { const resolvedIds = []; for (const item of refArray) { if (item && typeof item === 'object' && item.ref) { // Handle reference object if (item.ref.id) { // Direct ID reference resolvedIds.push(item.ref.id); getLogger().debug({ entityType, resolvedId: item.ref.id, method: 'direct_id' }, 'Resolved reference by direct ID'); } else if ((item.ref.name || item.ref.key) && this.entityRouter) { // Name or key reference - need to resolve const identifier = item.ref.name || item.ref.key; const foundEntity = await this.entityRouter.findEntityByIdOrKey(entityType, identifier, projectId); if (foundEntity && foundEntity.id) { resolvedIds.push(foundEntity.id); getLogger().info({ entityType, identifier, resolvedId: foundEntity.id, method: item.ref.name ? 'name_lookup' : 'key_lookup' }, 'Resolved reference by identifier'); } else { getLogger().warn({ entityType, identifier, projectId, method: 'lookup_failed' }, 'Could not resolve entity reference - entity not found'); } } } else if (typeof item === 'string' || typeof item === 'number') { // Direct ID value resolvedIds.push(String(item)); getLogger().debug({ entityType, resolvedId: String(item), method: 'direct_value' }, 'Resolved reference by direct value'); } } return resolvedIds; } /** * Resolves a single reference object to an ID * @param refObject Reference object like {ref: {name: "Name"}} or {ref: {id: "123"}} * @param entityType Type of entity being referenced * @param projectId Project ID for scoped searches * @returns Resolved ID or null if not found */ async resolveSingleReference(refObject, entityType, projectId) { if (!refObject || !refObject.ref) { return null; } if (refObject.ref.id) { // Direct ID reference return refObject.ref.id; } if ((refObject.ref.name || refObject.ref.key) && this.entityRouter) { // Name or key reference - need to resolve const identifier = refObject.ref.name || refObject.ref.key; const foundEntity = await this.entityRouter.findEntityByIdOrKey(entityType, identifier, projectId); if (foundEntity && foundEntity.id) { getLogger().info({ entityType, identifier, resolvedId: foundEntity.id, method: refObject.ref.name ? 'name_lookup' : 'key_lookup' }, 'Resolved single reference by identifier'); return foundEntity.id; } else { getLogger().warn({ entityType, identifier, projectId, method: 'single_lookup_failed' }, 'Could not resolve single entity reference - entity not found'); } } return null; } } //# sourceMappingURL=ReferenceResolver.js.map