@memberjunction/react-runtime 2.98.0 → 2.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.turbo/turbo-build.log +15 -20
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +171 -1
  4. package/dist/compiler/component-compiler.d.ts.map +1 -1
  5. package/dist/compiler/component-compiler.js +59 -8
  6. package/dist/compiler/component-compiler.js.map +1 -1
  7. package/dist/component-manager/component-manager.d.ts +39 -0
  8. package/dist/component-manager/component-manager.d.ts.map +1 -0
  9. package/dist/component-manager/component-manager.js +474 -0
  10. package/dist/component-manager/component-manager.js.map +1 -0
  11. package/dist/component-manager/index.d.ts +3 -0
  12. package/dist/component-manager/index.d.ts.map +1 -0
  13. package/dist/component-manager/index.js +6 -0
  14. package/dist/component-manager/index.js.map +1 -0
  15. package/dist/component-manager/types.d.ts +62 -0
  16. package/dist/component-manager/types.d.ts.map +1 -0
  17. package/dist/component-manager/types.js +3 -0
  18. package/dist/component-manager/types.js.map +1 -0
  19. package/dist/index.d.ts +7 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +18 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/registry/component-registry-service.d.ts +16 -1
  24. package/dist/registry/component-registry-service.d.ts.map +1 -1
  25. package/dist/registry/component-registry-service.js +212 -10
  26. package/dist/registry/component-registry-service.js.map +1 -1
  27. package/dist/registry/component-registry.d.ts +1 -1
  28. package/dist/registry/component-registry.d.ts.map +1 -1
  29. package/dist/registry/component-registry.js.map +1 -1
  30. package/dist/registry/component-resolver.d.ts.map +1 -1
  31. package/dist/registry/component-resolver.js +122 -52
  32. package/dist/registry/component-resolver.js.map +1 -1
  33. package/dist/registry/index.d.ts +1 -1
  34. package/dist/registry/index.d.ts.map +1 -1
  35. package/dist/registry/index.js.map +1 -1
  36. package/dist/runtime/component-hierarchy.d.ts +4 -0
  37. package/dist/runtime/component-hierarchy.d.ts.map +1 -1
  38. package/dist/runtime/component-hierarchy.js +127 -12
  39. package/dist/runtime/component-hierarchy.js.map +1 -1
  40. package/dist/runtime.umd.js +535 -1
  41. package/dist/types/index.d.ts +1 -0
  42. package/dist/types/index.d.ts.map +1 -1
  43. package/dist/types/index.js.map +1 -1
  44. package/dist/utilities/component-unwrapper.d.ts +7 -0
  45. package/dist/utilities/component-unwrapper.d.ts.map +1 -0
  46. package/dist/utilities/component-unwrapper.js +369 -0
  47. package/dist/utilities/component-unwrapper.js.map +1 -0
  48. package/dist/utilities/index.d.ts +1 -0
  49. package/dist/utilities/index.d.ts.map +1 -1
  50. package/dist/utilities/index.js +1 -0
  51. package/dist/utilities/index.js.map +1 -1
  52. package/dist/utilities/library-loader.d.ts +3 -0
  53. package/dist/utilities/library-loader.d.ts.map +1 -1
  54. package/dist/utilities/library-loader.js +101 -17
  55. package/dist/utilities/library-loader.js.map +1 -1
  56. package/examples/component-registry-integration.ts +191 -0
  57. package/package.json +6 -5
  58. package/src/compiler/component-compiler.ts +101 -23
  59. package/src/component-manager/component-manager.ts +736 -0
  60. package/src/component-manager/index.ts +13 -0
  61. package/src/component-manager/types.ts +204 -0
  62. package/src/index.ts +37 -1
  63. package/src/registry/component-registry-service.ts +315 -18
  64. package/src/registry/component-registry.ts +1 -1
  65. package/src/registry/component-resolver.ts +159 -67
  66. package/src/registry/index.ts +1 -1
  67. package/src/runtime/component-hierarchy.ts +124 -13
  68. package/src/types/index.ts +2 -0
  69. package/src/utilities/component-unwrapper.ts +481 -0
  70. package/src/utilities/index.ts +2 -1
  71. package/src/utilities/library-loader.ts +155 -22
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview Unified Component Manager for MemberJunction React Runtime
3
+ * Consolidates component fetching, compilation, registration, and usage tracking
4
+ * into a single, efficient manager that eliminates duplicate operations.
5
+ */
6
+
7
+ export { ComponentManager } from './component-manager';
8
+ export type {
9
+ LoadOptions,
10
+ LoadResult,
11
+ HierarchyResult,
12
+ ComponentManagerConfig
13
+ } from './types';
@@ -0,0 +1,204 @@
1
+ /**
2
+ * @fileoverview Type definitions for the unified ComponentManager
3
+ */
4
+
5
+ import { ComponentSpec } from '@memberjunction/interactive-component-types';
6
+ import { UserInfo } from '@memberjunction/core';
7
+ import { ComponentLibraryEntity } from '@memberjunction/core-entities';
8
+ import { ComponentObject } from '../types';
9
+
10
+ /**
11
+ * Resolution mode for specs
12
+ */
13
+ export type ResolutionMode = 'embed' | 'preserve-metadata';
14
+
15
+ /**
16
+ * Options for loading a component
17
+ */
18
+ export interface LoadOptions {
19
+ /**
20
+ * User context for database operations and registry fetching
21
+ */
22
+ contextUser?: UserInfo;
23
+
24
+ /**
25
+ * Force re-fetch from registry even if cached
26
+ */
27
+ forceRefresh?: boolean;
28
+
29
+ /**
30
+ * Force recompilation even if compiled version exists
31
+ */
32
+ forceRecompile?: boolean;
33
+
34
+ /**
35
+ * Whether this is a dependent component (for tracking)
36
+ */
37
+ isDependent?: boolean;
38
+
39
+ /**
40
+ * What to return from the load operation
41
+ */
42
+ returnType?: 'component' | 'spec' | 'both';
43
+
44
+ /**
45
+ * Enable registry usage tracking for licensing (default: true)
46
+ */
47
+ trackUsage?: boolean;
48
+
49
+ /**
50
+ * Namespace to use if not specified in spec
51
+ */
52
+ defaultNamespace?: string;
53
+
54
+ /**
55
+ * Version to use if not specified in spec
56
+ */
57
+ defaultVersion?: string;
58
+
59
+ /**
60
+ * How to format resolved specs:
61
+ * - 'preserve-metadata': Keep registry/namespace/name intact (for UI display)
62
+ * - 'embed': Convert to embedded format (for test harness)
63
+ * @default 'preserve-metadata'
64
+ */
65
+ resolutionMode?: ResolutionMode;
66
+
67
+ /**
68
+ * All available component libraries (for browser context where ComponentMetadataEngine isn't available)
69
+ */
70
+ allLibraries?: ComponentLibraryEntity[];
71
+ }
72
+
73
+ /**
74
+ * Result of loading a single component
75
+ */
76
+ export interface LoadResult {
77
+ /**
78
+ * Whether the load operation succeeded
79
+ */
80
+ success: boolean;
81
+
82
+ /**
83
+ * The compiled component object
84
+ */
85
+ component?: ComponentObject;
86
+
87
+ /**
88
+ * The fully resolved component specification
89
+ */
90
+ spec?: ComponentSpec;
91
+
92
+ /**
93
+ * Whether the component was loaded from cache
94
+ */
95
+ fromCache: boolean;
96
+
97
+ /**
98
+ * Any errors that occurred during loading
99
+ */
100
+ errors?: Array<{
101
+ message: string;
102
+ phase: 'fetch' | 'compile' | 'register' | 'dependency';
103
+ componentName?: string;
104
+ }>;
105
+
106
+ /**
107
+ * Components that were loaded as dependencies
108
+ */
109
+ dependencies?: Record<string, ComponentObject>;
110
+ }
111
+
112
+ /**
113
+ * Result of loading a component hierarchy
114
+ */
115
+ export interface HierarchyResult {
116
+ /**
117
+ * Whether the entire hierarchy loaded successfully
118
+ */
119
+ success: boolean;
120
+
121
+ /**
122
+ * The root component object
123
+ */
124
+ rootComponent?: ComponentObject;
125
+
126
+ /**
127
+ * The fully resolved root specification
128
+ */
129
+ resolvedSpec?: ComponentSpec;
130
+
131
+ /**
132
+ * List of all component names that were loaded
133
+ */
134
+ loadedComponents: string[];
135
+
136
+ /**
137
+ * Any errors that occurred during loading
138
+ */
139
+ errors: Array<{
140
+ message: string;
141
+ phase: 'fetch' | 'compile' | 'register' | 'dependency';
142
+ componentName?: string;
143
+ }>;
144
+
145
+ /**
146
+ * Map of all loaded components by name
147
+ */
148
+ components?: Record<string, ComponentObject>;
149
+
150
+ /**
151
+ * Number of components loaded from cache vs fetched
152
+ */
153
+ stats?: {
154
+ fromCache: number;
155
+ fetched: number;
156
+ compiled: number;
157
+ totalTime: number;
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Configuration for ComponentManager
163
+ */
164
+ export interface ComponentManagerConfig {
165
+ /**
166
+ * Enable debug logging
167
+ */
168
+ debug?: boolean;
169
+
170
+ /**
171
+ * Maximum cache size for fetched specs
172
+ */
173
+ maxCacheSize?: number;
174
+
175
+ /**
176
+ * Cache TTL in milliseconds (default: 1 hour)
177
+ */
178
+ cacheTTL?: number;
179
+
180
+ /**
181
+ * Whether to track registry usage for licensing
182
+ */
183
+ enableUsageTracking?: boolean;
184
+
185
+ /**
186
+ * Batch size for parallel dependency loading
187
+ */
188
+ dependencyBatchSize?: number;
189
+
190
+ /**
191
+ * Timeout for registry fetch operations (ms)
192
+ */
193
+ fetchTimeout?: number;
194
+ }
195
+
196
+ /**
197
+ * Internal cache entry for fetched specs
198
+ */
199
+ export interface CacheEntry {
200
+ spec: ComponentSpec;
201
+ fetchedAt: Date;
202
+ hash?: string;
203
+ usageNotified: boolean;
204
+ }
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  import { ComponentCompiler } from './compiler';
9
9
  import { ComponentRegistry } from './registry';
10
10
  import { ComponentResolver } from './registry';
11
+ import { ComponentManager } from './component-manager';
11
12
 
12
13
  // Export all types
13
14
  export * from './types';
@@ -30,9 +31,19 @@ export {
30
31
  ComponentResolver,
31
32
  ComponentSpec,
32
33
  ResolvedComponents,
33
- ComponentRegistryService
34
+ ComponentRegistryService,
35
+ IComponentRegistryClient
34
36
  } from './registry';
35
37
 
38
+ // Export unified ComponentManager
39
+ export { ComponentManager } from './component-manager';
40
+ export type {
41
+ LoadOptions,
42
+ LoadResult,
43
+ HierarchyResult,
44
+ ComponentManagerConfig
45
+ } from './component-manager';
46
+
36
47
  // Export runtime APIs
37
48
  export {
38
49
  createErrorBoundary,
@@ -117,6 +128,16 @@ export {
117
128
  CacheOptions
118
129
  } from './utilities/cache-manager';
119
130
 
131
+ export {
132
+ unwrapLibraryComponent,
133
+ unwrapLibraryComponents,
134
+ unwrapAllLibraryComponents,
135
+ // Legacy exports for backward compatibility
136
+ unwrapComponent,
137
+ unwrapComponents,
138
+ unwrapAllComponents
139
+ } from './utilities/component-unwrapper';
140
+
120
141
  // Version information
121
142
  export const VERSION = '2.69.1';
122
143
 
@@ -153,6 +174,7 @@ export function createReactRuntime(
153
174
  config?: {
154
175
  compiler?: Partial<import('./types').CompilerConfig>;
155
176
  registry?: Partial<import('./types').RegistryConfig>;
177
+ manager?: Partial<import('./component-manager').ComponentManagerConfig>;
156
178
  },
157
179
  runtimeContext?: import('./types').RuntimeContext,
158
180
  debug: boolean = false
@@ -168,16 +190,30 @@ export function createReactRuntime(
168
190
  debug: config?.registry?.debug ?? debug
169
191
  };
170
192
 
193
+ const managerConfig = {
194
+ ...config?.manager,
195
+ debug: config?.manager?.debug ?? debug
196
+ };
197
+
171
198
  const compiler = new ComponentCompiler(compilerConfig);
172
199
  compiler.setBabelInstance(babelInstance);
173
200
 
174
201
  const registry = new ComponentRegistry(registryConfig);
175
202
  const resolver = new ComponentResolver(registry, compiler, runtimeContext);
203
+
204
+ // Create the unified ComponentManager
205
+ const manager = new ComponentManager(
206
+ compiler,
207
+ registry,
208
+ runtimeContext || { React: null, ReactDOM: null },
209
+ managerConfig
210
+ );
176
211
 
177
212
  return {
178
213
  compiler,
179
214
  registry,
180
215
  resolver,
216
+ manager, // New unified manager
181
217
  version: VERSION,
182
218
  debug
183
219
  };
@@ -22,15 +22,36 @@ import {
22
22
  ComponentMetadataEngine
23
23
  } from '@memberjunction/core-entities';
24
24
 
25
+ // Type-only import for TypeScript - won't be included in UMD bundle
26
+ import type { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';
27
+
28
+ // Dynamic import of GraphQLComponentRegistryClient to avoid breaking UMD build
29
+ let GraphQLComponentRegistryClient: any;
30
+
31
+
25
32
  /**
26
33
  * Cached compiled component with metadata
27
34
  */
28
35
  interface CachedCompiledComponent {
29
- component: ComponentObject;
36
+ component: (context: RuntimeContext, styles?: any, components?: Record<string, any>) => ComponentObject;
30
37
  metadata: RegistryComponentResponse['metadata'];
31
38
  compiledAt: Date;
32
39
  lastUsed: Date;
33
40
  useCount: number;
41
+ specHash?: string; // SHA-256 hash of the spec used for compilation
42
+ }
43
+
44
+ /**
45
+ * GraphQL client interface for registry operations
46
+ */
47
+ export interface IComponentRegistryClient {
48
+ GetRegistryComponent(params: {
49
+ registryName: string;
50
+ namespace: string;
51
+ name: string;
52
+ version?: string;
53
+ hash?: string;
54
+ }): Promise<ComponentSpec | null>;
34
55
  }
35
56
 
36
57
  /**
@@ -49,15 +70,18 @@ export class ComponentRegistryService {
49
70
  private componentEngine = ComponentMetadataEngine.Instance;
50
71
  private registryProviders = new Map<string, RegistryProvider>();
51
72
  private debug: boolean = false;
73
+ private graphQLClient?: IComponentRegistryClient;
52
74
 
53
75
  private constructor(
54
76
  compiler: ComponentCompiler,
55
77
  runtimeContext: RuntimeContext,
56
- debug: boolean = false
78
+ debug: boolean = false,
79
+ graphQLClient?: IComponentRegistryClient
57
80
  ) {
58
81
  this.compiler = compiler;
59
82
  this.runtimeContext = runtimeContext;
60
83
  this.debug = debug;
84
+ this.graphQLClient = graphQLClient;
61
85
  }
62
86
 
63
87
  /**
@@ -66,14 +90,88 @@ export class ComponentRegistryService {
66
90
  static getInstance(
67
91
  compiler: ComponentCompiler,
68
92
  context: RuntimeContext,
69
- debug: boolean = false
93
+ debug: boolean = false,
94
+ graphQLClient?: IComponentRegistryClient
70
95
  ): ComponentRegistryService {
71
96
  if (!ComponentRegistryService.instance) {
72
- ComponentRegistryService.instance = new ComponentRegistryService(compiler, context, debug);
97
+ ComponentRegistryService.instance = new ComponentRegistryService(compiler, context, debug, graphQLClient);
73
98
  }
74
99
  return ComponentRegistryService.instance;
75
100
  }
76
101
 
102
+ /**
103
+ * Set the GraphQL client for registry operations
104
+ */
105
+ setGraphQLClient(client: IComponentRegistryClient): void {
106
+ this.graphQLClient = client;
107
+ if (this.debug) {
108
+ console.log('✅ GraphQL client configured for component registry');
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Cached GraphQL client instance created from Metadata.Provider
114
+ */
115
+ private cachedProviderClient: IComponentRegistryClient | null = null;
116
+
117
+ /**
118
+ * Get the GraphQL client, using the provided one or falling back to creating one with Metadata.Provider
119
+ * @returns The GraphQL client if available
120
+ */
121
+ private async getGraphQLClient(): Promise<IComponentRegistryClient | null> {
122
+ // If explicitly set, use that
123
+ if (this.graphQLClient) {
124
+ return this.graphQLClient;
125
+ }
126
+
127
+ // If we've already created one from the provider, reuse it
128
+ if (this.cachedProviderClient) {
129
+ return this.cachedProviderClient;
130
+ }
131
+
132
+ // Try to create GraphQLComponentRegistryClient with Metadata.Provider
133
+ try {
134
+ const provider = Metadata?.Provider;
135
+ if (provider && (provider as any).ExecuteGQL !== undefined) {
136
+ // Dynamically load GraphQLComponentRegistryClient if not already loaded
137
+ if (!GraphQLComponentRegistryClient) {
138
+ try {
139
+ const graphqlModule = await import('@memberjunction/graphql-dataprovider');
140
+ GraphQLComponentRegistryClient = graphqlModule.GraphQLComponentRegistryClient;
141
+ } catch (importError) {
142
+ if (this.debug) {
143
+ console.log('⚠️ [ComponentRegistryService] @memberjunction/graphql-dataprovider not available');
144
+ }
145
+ return null;
146
+ }
147
+ }
148
+
149
+ // Create the client if we have the class
150
+ if (GraphQLComponentRegistryClient) {
151
+ try {
152
+ const client = new GraphQLComponentRegistryClient(provider as GraphQLDataProvider);
153
+ this.cachedProviderClient = client;
154
+ if (this.debug) {
155
+ console.log('📡 [ComponentRegistryService] Created GraphQL client from Metadata.Provider');
156
+ }
157
+ return client;
158
+ } catch (error) {
159
+ if (this.debug) {
160
+ console.log('⚠️ [ComponentRegistryService] Failed to create GraphQL client:', error);
161
+ }
162
+ }
163
+ }
164
+ }
165
+ } catch (error) {
166
+ // Provider might not be available in all environments
167
+ if (this.debug) {
168
+ console.log('⚠️ [ComponentRegistryService] Could not access Metadata.Provider:', error);
169
+ }
170
+ }
171
+
172
+ return null;
173
+ }
174
+
77
175
  /**
78
176
  * Initialize the service with metadata
79
177
  */
@@ -82,6 +180,34 @@ export class ComponentRegistryService {
82
180
  await this.componentEngine.Config(false, contextUser);
83
181
  }
84
182
 
183
+ /**
184
+ * Calculate SHA-256 hash of a component spec for cache comparison
185
+ * Uses Web Crypto API which is available in modern browsers and Node.js 15+
186
+ */
187
+ private async calculateSpecHash(spec: ComponentSpec): Promise<string> {
188
+ // Check for crypto.subtle availability
189
+ if (typeof crypto === 'undefined' || !crypto.subtle) {
190
+ throw new Error(
191
+ 'Web Crypto API not available. This typically happens when running in an insecure context. ' +
192
+ 'Please use HTTPS or localhost for development. ' +
193
+ 'Note: crypto.subtle is available in Node.js 15+ and all modern browsers on secure contexts.'
194
+ );
195
+ }
196
+
197
+ const specString = JSON.stringify(spec);
198
+ const encoder = new TextEncoder();
199
+ const data = encoder.encode(specString);
200
+
201
+ // Calculate SHA-256 hash using Web Crypto API
202
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
203
+
204
+ // Convert ArrayBuffer to hex string
205
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
206
+ const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
207
+
208
+ return hashHex;
209
+ }
210
+
85
211
  /**
86
212
  * Get a compiled component, using cache if available
87
213
  */
@@ -114,7 +240,8 @@ export class ComponentRegistryService {
114
240
  if (this.debug) {
115
241
  console.log(`✅ Reusing compiled component from cache: ${key} (use count: ${cached.useCount})`);
116
242
  }
117
- return cached.component;
243
+ // Call the factory function to get the ComponentObject
244
+ return cached.component(this.runtimeContext);
118
245
  }
119
246
 
120
247
  // Not in cache, need to load and compile
@@ -169,9 +296,9 @@ export class ComponentRegistryService {
169
296
  if (!compilationResult.component) {
170
297
  throw new Error(`Component compilation succeeded but no component returned`);
171
298
  }
172
- const compiledComponent = compilationResult.component.factory(this.runtimeContext);
299
+ const compiledComponentFactory = compilationResult.component.factory;
173
300
  this.compiledComponentCache.set(key, {
174
- component: compiledComponent,
301
+ component: compiledComponentFactory,
175
302
  metadata,
176
303
  compiledAt: new Date(),
177
304
  lastUsed: new Date(),
@@ -183,7 +310,158 @@ export class ComponentRegistryService {
183
310
  this.addComponentReference(key, referenceId);
184
311
  }
185
312
 
186
- return compiledComponent;
313
+ // Call the factory function to get the ComponentObject
314
+ return compiledComponentFactory(this.runtimeContext);
315
+ }
316
+
317
+ /**
318
+ * Get compiled component from external registry by registry name
319
+ * This is used when spec.registry field is populated
320
+ */
321
+ async getCompiledComponentFromRegistry(
322
+ registryName: string,
323
+ namespace: string,
324
+ name: string,
325
+ version: string,
326
+ referenceId?: string,
327
+ contextUser?: UserInfo
328
+ ): Promise<any> {
329
+ await this.initialize(contextUser);
330
+
331
+ if (this.debug) {
332
+ console.log(`🌐 [ComponentRegistryService] Fetching from external registry: ${registryName}/${namespace}/${name}@${version}`);
333
+ }
334
+
335
+ // Find the registry by name in ComponentRegistries
336
+ const registry = this.componentEngine.ComponentRegistries?.find(
337
+ r => r.Name === registryName && r.Status === 'Active'
338
+ );
339
+
340
+ if (!registry) {
341
+ throw new Error(`Registry not found or inactive: ${registryName}`);
342
+ }
343
+
344
+ if (this.debug) {
345
+ console.log(`✅ [ComponentRegistryService] Found registry: ${registry.Name} (ID: ${registry.ID})`);
346
+ }
347
+
348
+ // Get GraphQL client - use provided one or fallback to Metadata.Provider
349
+ const graphQLClient = await this.getGraphQLClient();
350
+ if (!graphQLClient) {
351
+ throw new Error('GraphQL client not available for external registry fetching. No client provided and Metadata.Provider is not a GraphQLDataProvider.');
352
+ }
353
+
354
+ // Check if we have a cached version first
355
+ const key = `external:${registryName}:${namespace}:${name}:${version}`;
356
+ const cached = this.compiledComponentCache.get(key);
357
+
358
+ try {
359
+ // Fetch component spec from external registry via MJServer
360
+ // Pass cached hash if available for efficient caching
361
+ const spec = await graphQLClient.GetRegistryComponent({
362
+ registryName: registry.Name, // Pass registry name, not ID
363
+ namespace,
364
+ name,
365
+ version,
366
+ hash: cached?.specHash // Pass cached hash if available
367
+ });
368
+
369
+ // If null returned, it means not modified (304)
370
+ if (!spec && cached?.specHash) {
371
+ if (this.debug) {
372
+ console.log(`♻️ [ComponentRegistryService] Component not modified, using cached: ${key}`);
373
+ }
374
+ cached.lastUsed = new Date();
375
+ cached.useCount++;
376
+
377
+ // Track reference
378
+ if (referenceId) {
379
+ this.addComponentReference(key, referenceId);
380
+ }
381
+
382
+ // Call the factory function to get the ComponentObject
383
+ return cached.component(this.runtimeContext);
384
+ }
385
+
386
+ if (!spec) {
387
+ throw new Error(`Component not found in registry ${registryName}: ${namespace}/${name}@${version}`);
388
+ }
389
+
390
+ if (this.debug) {
391
+ console.log(`✅ [ComponentRegistryService] Fetched spec from external registry: ${spec.name}`);
392
+ }
393
+
394
+ // Calculate hash of the fetched spec
395
+ const specHash = await this.calculateSpecHash(spec);
396
+
397
+ // Check if hash matches cached version (shouldn't happen if server works correctly)
398
+ if (cached && cached.specHash === specHash) {
399
+ if (this.debug) {
400
+ console.log(`♻️ [ComponentRegistryService] Using cached compilation for: ${key} (hash match)`);
401
+ }
402
+ cached.lastUsed = new Date();
403
+ cached.useCount++;
404
+
405
+ // Track reference
406
+ if (referenceId) {
407
+ this.addComponentReference(key, referenceId);
408
+ }
409
+
410
+ // Call the factory function to get the ComponentObject
411
+ return cached.component(this.runtimeContext);
412
+ }
413
+
414
+ // Spec has changed or is new, need to compile
415
+ if (cached && this.debug) {
416
+ console.log(`🔄 [ComponentRegistryService] Spec changed for: ${key}, recompiling (old hash: ${cached.specHash?.substring(0, 8)}..., new hash: ${specHash.substring(0, 8)}...)`);
417
+ }
418
+
419
+ // Load all libraries from metadata engine
420
+ const allLibraries = this.componentEngine.ComponentLibraries || [];
421
+
422
+ // Compile the component
423
+ const compilationResult = await this.compiler.compile({
424
+ componentName: spec.name,
425
+ componentCode: spec.code || '',
426
+ allLibraries: allLibraries
427
+ });
428
+
429
+ if (!compilationResult.success || !compilationResult.component) {
430
+ throw new Error(`Failed to compile component: ${compilationResult.error?.message || 'Unknown error'}`);
431
+ }
432
+
433
+ // Cache the compiled component with spec hash
434
+ this.compiledComponentCache.set(key, {
435
+ component: compilationResult.component.factory,
436
+ metadata: {
437
+ name: spec.name,
438
+ namespace: spec.namespace || '',
439
+ version: spec.version || '1.0.0',
440
+ description: spec.description || '',
441
+ type: spec.type,
442
+ isLocal: false // This is from an external registry
443
+ },
444
+ compiledAt: new Date(),
445
+ lastUsed: new Date(),
446
+ useCount: 1,
447
+ specHash: specHash // Store the hash for future comparison
448
+ });
449
+
450
+ // Track reference
451
+ if (referenceId) {
452
+ this.addComponentReference(key, referenceId);
453
+ }
454
+
455
+ if (this.debug) {
456
+ console.log(`🎯 [ComponentRegistryService] Successfully compiled external component: ${spec.name}`);
457
+ }
458
+
459
+ // Call the factory function to get the ComponentObject
460
+ return compilationResult.component.factory(this.runtimeContext);
461
+ } catch (error) {
462
+ console.error(`❌ [ComponentRegistryService] Failed to fetch from external registry:`, error);
463
+ throw error;
464
+ }
187
465
  }
188
466
 
189
467
  /**
@@ -226,17 +504,36 @@ export class ComponentRegistryService {
226
504
  throw new Error(`Registry not found: ${component.SourceRegistryID}`);
227
505
  }
228
506
 
229
- if (!registry) {
230
- throw new Error(`Registry not found: ${component.SourceRegistryID}`);
231
- }
507
+ // Try GraphQL client first if available
508
+ let spec: ComponentSpec;
232
509
 
233
- const spec = await this.fetchFromExternalRegistry(
234
- registry.URI || '',
235
- component.Name,
236
- component.Namespace || '',
237
- component.Version,
238
- this.getRegistryApiKey(registry.ID) // API keys stored in env vars or secure config
239
- );
510
+ if (this.graphQLClient) {
511
+ if (this.debug) {
512
+ console.log(`Fetching from registry via GraphQL: ${component.Name}`);
513
+ }
514
+
515
+ const result = await this.graphQLClient.GetRegistryComponent({
516
+ registryName: registry.Name,
517
+ namespace: component.Namespace || '',
518
+ name: component.Name,
519
+ version: component.Version
520
+ });
521
+
522
+ if (!result) {
523
+ throw new Error(`Component not found in registry: ${component.Name}`);
524
+ }
525
+
526
+ spec = result;
527
+ } else {
528
+ // Fallback to direct HTTP if no GraphQL client
529
+ spec = await this.fetchFromExternalRegistry(
530
+ registry.URI || '',
531
+ component.Name,
532
+ component.Namespace || '',
533
+ component.Version,
534
+ this.getRegistryApiKey(registry.ID)
535
+ );
536
+ }
240
537
 
241
538
  // Store in local database for future use
242
539
  await this.cacheExternalComponent(componentId, spec, contextUser);