@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
JavaScript
;
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