UNPKG

@memberjunction/react-runtime

Version:

Platform-agnostic React component runtime for MemberJunction. Provides core compilation, registry, and execution capabilities for React components in any JavaScript environment.

359 lines 17.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ComponentResolver = void 0; const component_registry_service_1 = require("./component-registry-service"); const core_entities_1 = require("@memberjunction/core-entities"); class ComponentResolver { constructor(registry, compiler, runtimeContext, debug = false) { this.registryService = null; this.compiler = null; this.runtimeContext = null; this.componentEngine = core_entities_1.ComponentMetadataEngine.Instance; this.debug = false; this.registry = registry; this.resolverInstanceId = `resolver-${Date.now()}-${Math.random()}`; this.debug = debug; if (compiler && runtimeContext) { this.compiler = compiler; this.runtimeContext = runtimeContext; this.registryService = component_registry_service_1.ComponentRegistryService.getInstance(compiler, runtimeContext, debug); } } async resolveComponents(spec, namespace = 'Global', contextUser) { console.log(`🚀 [ComponentResolver] Starting component resolution for: ${spec.name}`); console.log(`📋 [ComponentResolver] Root component spec:`, { name: spec.name, location: spec.location, registry: spec.registry, namespace: spec.namespace, hasCode: !!spec.code, hasDependencies: !!(spec.dependencies && spec.dependencies.length > 0) }); if (this.debug) { console.log(`📋 [ComponentResolver] Dependencies to resolve:`, (spec.dependencies || []).map(d => ({ name: d.name, location: d.location, namespace: d.namespace }))); } const resolved = {}; if (this.registryService) { if (this.debug) { console.log(`🔄 [ComponentResolver] Initializing component engine...`); } await this.componentEngine.Config(false, contextUser); if (this.debug) { console.log(`✅ [ComponentResolver] Component engine initialized with ${this.componentEngine.Components?.length || 0} components`); } } await this.resolveComponentHierarchy(spec, resolved, namespace, new Set(), contextUser); if (!resolved[spec.name]) { console.error(`❌ [ComponentResolver] Root component '${spec.name}' was NOT added to resolved map!`); console.log(`📦 [ComponentResolver] What IS in resolved map:`, Object.keys(resolved)); } if (this.debug) { console.log(`📊 [ComponentResolver] Resolved components before unwrapping:`, Object.keys(resolved)); } const unwrapped = {}; for (const [name, value] of Object.entries(resolved)) { if (value && typeof value === 'object' && 'component' in value) { if (typeof value.component === 'function') { unwrapped[name] = value.component; if (this.debug) { console.log(`✅ [ComponentResolver] Unwrapped component: ${name} (was object with .component)`); } } else { console.error(`❌ [ComponentResolver] Component ${name} has invalid component property:`, typeof value.component, value); unwrapped[name] = value; } } else if (typeof value === 'function') { unwrapped[name] = value; if (this.debug) { console.log(`✅ [ComponentResolver] Component already a function: ${name}`); } } else { console.warn(`⚠️ [ComponentResolver] Component ${name} is not a function or wrapped component:`, typeof value, value); unwrapped[name] = value; } } if (this.debug) { console.log(`🎯 [ComponentResolver] Final resolved components:`, Object.keys(unwrapped).map(name => ({ name, type: typeof unwrapped[name], isUndefined: unwrapped[name] === undefined }))); } return unwrapped; } async resolveComponentHierarchy(spec, resolved, namespace, visited = new Set(), contextUser) { const componentId = `${spec.namespace || namespace}/${spec.name}@${spec.version || 'latest'}`; if (resolved[spec.name]) { if (this.debug) { console.log(`⏭️ [ComponentResolver] Component already resolved: ${spec.name}`); } return; } if (visited.has(componentId)) { if (this.debug) { console.warn(`Circular dependency detected for component: ${componentId}`); } return; } visited.add(componentId); if (this.debug) { console.log(`🔄 [ComponentResolver] Resolving dependencies for ${spec.name} BEFORE resolving itself`); } const children = spec.dependencies || []; for (const child of children) { if (this.debug) { console.log(` ↳ [ComponentResolver] Resolving dependency: ${child.name} for parent ${spec.name}`); } await this.resolveComponentHierarchy(child, resolved, namespace, visited, contextUser); } if (children.length > 0 && this.debug) { console.log(`✅ [ComponentResolver] All ${children.length} dependencies resolved for ${spec.name}, now resolving itself`); } if (spec.location === 'registry' && this.registryService) { if (this.debug) { console.log(`🔍 [ComponentResolver] Looking for registry component: ${spec.name} in namespace: ${spec.namespace || namespace}`); if (spec.registry) { console.log(` 📍 [ComponentResolver] External registry specified: ${spec.registry}`); } else { console.log(` 📍 [ComponentResolver] Local registry (no registry field specified)`); } } try { if (spec.registry) { if (this.debug) { console.log(`🌐 [ComponentResolver] Fetching from external registry: ${spec.registry}`); } const compiledComponent = await this.registryService.getCompiledComponentFromRegistry(spec.registry, spec.namespace || namespace, spec.name, spec.version || 'latest', this.resolverInstanceId, contextUser); if (compiledComponent) { resolved[spec.name] = compiledComponent; if (this.debug) { console.log(`✅ [ComponentResolver] Successfully fetched and compiled from external registry: ${spec.name}`); } } else { console.error(`❌ [ComponentResolver] Failed to fetch from external registry: ${spec.name} from ${spec.registry}`); } } else { if (this.debug) { console.log(`💾 [ComponentResolver] Looking for locally registered component`); } const allComponents = this.componentEngine.Components || []; if (this.debug) { console.log(`📊 [ComponentResolver] Total components in engine: ${allComponents.length}`); } const matchingNames = allComponents.filter((c) => c.Name === spec.name); if (matchingNames.length > 0 && this.debug) { console.log(`🔎 [ComponentResolver] Found ${matchingNames.length} components with name "${spec.name}":`, matchingNames.map((c) => ({ ID: c.ID, Name: c.Name, Namespace: c.Namespace, Version: c.Version, Status: c.Status }))); } const component = this.componentEngine.Components?.find((c) => c.Name === spec.name && c.Namespace === (spec.namespace || namespace)); if (component) { if (this.debug) { console.log(`✅ [ComponentResolver] Found component in local DB:`, { ID: component.ID, Name: component.Name, Namespace: component.Namespace, Version: component.Version }); } const compiledComponent = await this.registryService.getCompiledComponent(component.ID, this.resolverInstanceId, contextUser); resolved[spec.name] = compiledComponent; if (this.debug) { console.log(`📦 [ComponentResolver] Successfully compiled and resolved local component: ${spec.name}, type: ${typeof compiledComponent}`); } } else { console.error(`❌ [ComponentResolver] Local registry component NOT found in database: ${spec.name} with namespace: ${spec.namespace || namespace}`); if (this.debug) { console.warn(`Local registry component not found in database: ${spec.name}`); } } } } catch (error) { if (this.debug) { console.error(`Failed to load registry component ${spec.name}:`, error); } } } else { const componentNamespace = spec.namespace || namespace; if (spec.code && this.compiler) { if (this.debug) { console.log(`🔨 [ComponentResolver] Component ${spec.name} has inline code, compiling...`); } try { const compilationResult = await this.compiler.compile({ componentName: spec.name, componentCode: spec.code, libraries: spec.libraries, dependencies: spec.dependencies, allLibraries: [] }); if (compilationResult.success && compilationResult.component) { if (!this.runtimeContext) { console.error(`❌ [ComponentResolver] Cannot compile without runtime context`); return; } const componentObject = compilationResult.component.factory(this.runtimeContext); this.registry.register(spec.name, componentObject, componentNamespace, spec.version || 'latest'); resolved[spec.name] = componentObject; if (this.debug) { console.log(`✅ [ComponentResolver] Successfully compiled and registered inline component: ${spec.name}`); } } else { console.error(`❌ [ComponentResolver] Failed to compile inline component ${spec.name}:`, compilationResult.error); } } catch (error) { console.error(`❌ [ComponentResolver] Error compiling inline component ${spec.name}:`, error); } } else { if (this.debug) { console.log(`🔍 [ComponentResolver] Looking for embedded component: ${spec.name} in namespace: ${componentNamespace}`); } const component = this.registry.get(spec.name, componentNamespace); if (component) { resolved[spec.name] = component; if (this.debug) { console.log(`✅ [ComponentResolver] Found embedded component: ${spec.name}, type: ${typeof component}`); } if (this.debug) { console.log(`📄 Resolved embedded component: ${spec.name} from namespace ${componentNamespace}, type:`, typeof component); } } else { if (this.debug) { console.log(`⚠️ [ComponentResolver] Not found in namespace ${componentNamespace}, trying fallback namespace: ${namespace}`); } const fallbackComponent = this.registry.get(spec.name, namespace); if (fallbackComponent) { resolved[spec.name] = fallbackComponent; if (this.debug) { console.log(`✅ [ComponentResolver] Found embedded component in fallback namespace: ${spec.name}, type: ${typeof fallbackComponent}`); } if (this.debug) { console.log(`📄 Resolved embedded component: ${spec.name} from fallback namespace ${namespace}, type:`, typeof fallbackComponent); } } else { console.error(`❌ [ComponentResolver] Could not resolve embedded component: ${spec.name} in namespace ${componentNamespace} or ${namespace}`); if (this.debug) { console.warn(`⚠️ Could not resolve embedded component: ${spec.name} in namespace ${componentNamespace} or ${namespace}`); } resolved[spec.name] = undefined; } } } } } cleanup() { if (this.registryService) { if (this.debug) { console.log(`Cleaning up resolver: ${this.resolverInstanceId}`); } } } validateDependencies(spec, namespace = 'Global') { const missing = []; const checked = new Set(); this.checkDependencies(spec, namespace, missing, checked); return missing; } checkDependencies(spec, namespace, missing, checked) { if (checked.has(spec.name)) return; checked.add(spec.name); if (!this.registry.has(spec.name, namespace)) { missing.push(spec.name); } const children = spec.dependencies || []; for (const child of children) { this.checkDependencies(child, namespace, missing, checked); } } getDependencyGraph(spec) { const graph = new Map(); const visited = new Set(); this.buildDependencyGraph(spec, graph, visited); return graph; } buildDependencyGraph(spec, graph, visited) { if (visited.has(spec.name)) return; visited.add(spec.name); const children = spec.dependencies || []; const dependencies = children.map(child => child.name); graph.set(spec.name, dependencies); for (const child of children) { this.buildDependencyGraph(child, graph, visited); } } getLoadOrder(spec) { const graph = this.getDependencyGraph(spec); const visited = new Set(); const stack = []; for (const node of graph.keys()) { if (!visited.has(node)) { this.topologicalSortDFS(node, graph, visited, stack); } } return stack.reverse(); } topologicalSortDFS(node, graph, visited, stack) { visited.add(node); const dependencies = graph.get(node) || []; for (const dep of dependencies) { if (!visited.has(dep)) { this.topologicalSortDFS(dep, graph, visited, stack); } } stack.push(node); } resolveInOrder(spec, namespace = 'Global') { const loadOrder = this.getLoadOrder(spec); const resolved = []; for (const name of loadOrder) { const component = this.registry.get(name, namespace); if (component) { resolved.push({ name, component }); } } return resolved; } flattenComponentSpecs(spec) { const flattened = []; const visited = new Set(); this.collectComponentSpecs(spec, flattened, visited); return flattened; } collectComponentSpecs(spec, collected, visited) { if (visited.has(spec.name)) return; visited.add(spec.name); collected.push(spec); const children = spec.dependencies || []; for (const child of children) { this.collectComponentSpecs(child, collected, visited); } } } exports.ComponentResolver = ComponentResolver; //# sourceMappingURL=component-resolver.js.map