@memberjunction/react-runtime 2.111.1 → 2.112.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 (43) hide show
  1. package/.turbo/turbo-build.log +10 -11
  2. package/CHANGELOG.md +6 -6
  3. package/dist/compiler/component-compiler.d.ts.map +1 -1
  4. package/dist/compiler/component-compiler.js +66 -56
  5. package/dist/compiler/component-compiler.js.map +1 -1
  6. package/dist/component-manager/component-manager.d.ts.map +1 -1
  7. package/dist/component-manager/component-manager.js +48 -42
  8. package/dist/component-manager/component-manager.js.map +1 -1
  9. package/dist/component-manager/types.d.ts +1 -1
  10. package/dist/component-manager/types.d.ts.map +1 -1
  11. package/dist/component-manager/types.js.map +1 -1
  12. package/dist/registry/component-registry-service.d.ts +1 -1
  13. package/dist/registry/component-registry-service.d.ts.map +1 -1
  14. package/dist/registry/component-registry-service.js +34 -34
  15. package/dist/registry/component-registry-service.js.map +1 -1
  16. package/dist/registry/component-resolver.d.ts +1 -1
  17. package/dist/registry/component-resolver.d.ts.map +1 -1
  18. package/dist/registry/component-resolver.js +10 -11
  19. package/dist/registry/component-resolver.js.map +1 -1
  20. package/dist/runtime/component-hierarchy.d.ts +1 -1
  21. package/dist/runtime/component-hierarchy.d.ts.map +1 -1
  22. package/dist/runtime/component-hierarchy.js +43 -43
  23. package/dist/runtime/component-hierarchy.js.map +1 -1
  24. package/dist/runtime/prop-builder.d.ts.map +1 -1
  25. package/dist/runtime/prop-builder.js +22 -24
  26. package/dist/runtime/prop-builder.js.map +1 -1
  27. package/dist/runtime.umd.js +494 -511
  28. package/dist/types/index.d.ts.map +1 -1
  29. package/dist/types/index.js.map +1 -1
  30. package/dist/utilities/library-registry.d.ts +1 -1
  31. package/dist/utilities/library-registry.d.ts.map +1 -1
  32. package/dist/utilities/library-registry.js +10 -10
  33. package/dist/utilities/library-registry.js.map +1 -1
  34. package/package.json +5 -6
  35. package/src/compiler/component-compiler.ts +186 -164
  36. package/src/component-manager/component-manager.ts +162 -216
  37. package/src/component-manager/types.ts +27 -27
  38. package/src/registry/component-registry-service.ts +190 -218
  39. package/src/registry/component-resolver.ts +87 -98
  40. package/src/runtime/component-hierarchy.ts +57 -94
  41. package/src/runtime/prop-builder.ts +28 -33
  42. package/src/types/index.ts +3 -4
  43. package/src/utilities/library-registry.ts +14 -19
@@ -6,20 +6,20 @@
6
6
  import { ComponentSpec, ComponentObject } from '@memberjunction/interactive-component-types';
7
7
  import { ComponentCompiler } from '../compiler';
8
8
  import { RuntimeContext } from '../types';
9
- import {
10
- RegistryProvider,
9
+ import {
10
+ RegistryProvider,
11
11
  RegistryComponentResponse,
12
12
  ComponentDependencyInfo,
13
- DependencyTree,
14
- RegistryComponentMetadata
13
+ DependencyTree,
14
+ RegistryComponentMetadata,
15
15
  } from './registry-provider';
16
- import { UserInfo, Metadata } from '@memberjunction/core';
17
- import {
18
- ComponentEntity,
16
+ import { UserInfo, Metadata } from '@memberjunction/global';
17
+ import {
18
+ ComponentEntity,
19
19
  ComponentRegistryEntity,
20
20
  ComponentDependencyEntity,
21
21
  ComponentLibraryLinkEntity,
22
- ComponentMetadataEngine
22
+ ComponentMetadataEngine,
23
23
  } from '@memberjunction/core-entities';
24
24
 
25
25
  // Type-only import for TypeScript - won't be included in UMD bundle
@@ -28,7 +28,6 @@ import type { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';
28
28
  // Dynamic import of GraphQLComponentRegistryClient to avoid breaking UMD build
29
29
  let GraphQLComponentRegistryClient: any;
30
30
 
31
-
32
31
  /**
33
32
  * Cached compiled component with metadata
34
33
  */
@@ -38,7 +37,7 @@ interface CachedCompiledComponent {
38
37
  compiledAt: Date;
39
38
  lastUsed: Date;
40
39
  useCount: number;
41
- specHash?: string; // SHA-256 hash of the spec used for compilation
40
+ specHash?: string; // SHA-256 hash of the spec used for compilation
42
41
  }
43
42
 
44
43
  /**
@@ -59,11 +58,11 @@ export interface IComponentRegistryClient {
59
58
  */
60
59
  export class ComponentRegistryService {
61
60
  private static instance: ComponentRegistryService | null = null;
62
-
61
+
63
62
  // Caches
64
63
  private compiledComponentCache = new Map<string, CachedCompiledComponent>();
65
64
  private componentReferences = new Map<string, Set<string>>();
66
-
65
+
67
66
  // Dependencies
68
67
  private compiler: ComponentCompiler;
69
68
  private runtimeContext: RuntimeContext;
@@ -71,7 +70,7 @@ export class ComponentRegistryService {
71
70
  private registryProviders = new Map<string, RegistryProvider>();
72
71
  private debug: boolean = false;
73
72
  private graphQLClient?: IComponentRegistryClient;
74
-
73
+
75
74
  private constructor(
76
75
  compiler: ComponentCompiler,
77
76
  runtimeContext: RuntimeContext,
@@ -83,12 +82,12 @@ export class ComponentRegistryService {
83
82
  this.debug = debug;
84
83
  this.graphQLClient = graphQLClient;
85
84
  }
86
-
85
+
87
86
  /**
88
87
  * Get or create the singleton instance
89
88
  */
90
89
  static getInstance(
91
- compiler: ComponentCompiler,
90
+ compiler: ComponentCompiler,
92
91
  context: RuntimeContext,
93
92
  debug: boolean = false,
94
93
  graphQLClient?: IComponentRegistryClient
@@ -98,7 +97,7 @@ export class ComponentRegistryService {
98
97
  }
99
98
  return ComponentRegistryService.instance;
100
99
  }
101
-
100
+
102
101
  /**
103
102
  * Set the GraphQL client for registry operations
104
103
  */
@@ -108,12 +107,12 @@ export class ComponentRegistryService {
108
107
  console.log('✅ GraphQL client configured for component registry');
109
108
  }
110
109
  }
111
-
110
+
112
111
  /**
113
112
  * Cached GraphQL client instance created from Metadata.Provider
114
113
  */
115
114
  private cachedProviderClient: IComponentRegistryClient | null = null;
116
-
115
+
117
116
  /**
118
117
  * Get the GraphQL client, using the provided one or falling back to creating one with Metadata.Provider
119
118
  * @returns The GraphQL client if available
@@ -123,12 +122,12 @@ export class ComponentRegistryService {
123
122
  if (this.graphQLClient) {
124
123
  return this.graphQLClient;
125
124
  }
126
-
125
+
127
126
  // If we've already created one from the provider, reuse it
128
127
  if (this.cachedProviderClient) {
129
128
  return this.cachedProviderClient;
130
129
  }
131
-
130
+
132
131
  // Try to create GraphQLComponentRegistryClient with Metadata.Provider
133
132
  try {
134
133
  const provider = Metadata?.Provider;
@@ -145,7 +144,7 @@ export class ComponentRegistryService {
145
144
  return null;
146
145
  }
147
146
  }
148
-
147
+
149
148
  // Create the client if we have the class
150
149
  if (GraphQLComponentRegistryClient) {
151
150
  try {
@@ -168,10 +167,10 @@ export class ComponentRegistryService {
168
167
  console.log('⚠️ [ComponentRegistryService] Could not access Metadata.Provider:', error);
169
168
  }
170
169
  }
171
-
170
+
172
171
  return null;
173
172
  }
174
-
173
+
175
174
  /**
176
175
  * Initialize the service with metadata
177
176
  */
@@ -179,7 +178,7 @@ export class ComponentRegistryService {
179
178
  // Initialize metadata engine
180
179
  await this.componentEngine.Config(false, contextUser);
181
180
  }
182
-
181
+
183
182
  /**
184
183
  * Calculate SHA-256 hash of a component spec for cache comparison
185
184
  * Uses Web Crypto API which is available in modern browsers and Node.js 15+
@@ -189,93 +188,88 @@ export class ComponentRegistryService {
189
188
  if (typeof crypto === 'undefined' || !crypto.subtle) {
190
189
  throw new Error(
191
190
  '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.'
191
+ 'Please use HTTPS or localhost for development. ' +
192
+ 'Note: crypto.subtle is available in Node.js 15+ and all modern browsers on secure contexts.'
194
193
  );
195
194
  }
196
-
195
+
197
196
  const specString = JSON.stringify(spec);
198
197
  const encoder = new TextEncoder();
199
198
  const data = encoder.encode(specString);
200
-
199
+
201
200
  // Calculate SHA-256 hash using Web Crypto API
202
201
  const hashBuffer = await crypto.subtle.digest('SHA-256', data);
203
-
202
+
204
203
  // Convert ArrayBuffer to hex string
205
204
  const hashArray = Array.from(new Uint8Array(hashBuffer));
206
- const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
207
-
205
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
206
+
208
207
  return hashHex;
209
208
  }
210
-
209
+
211
210
  /**
212
211
  * Get a compiled component, using cache if available
213
212
  */
214
- async getCompiledComponent(
215
- componentId: string,
216
- referenceId?: string,
217
- contextUser?: UserInfo
218
- ): Promise<ComponentObject> {
213
+ async getCompiledComponent(componentId: string, referenceId?: string, contextUser?: UserInfo): Promise<ComponentObject> {
219
214
  await this.initialize(contextUser);
220
-
215
+
221
216
  // Find component in metadata
222
217
  const component = this.componentEngine.Components.find((c: ComponentEntity) => c.ID === componentId);
223
218
  if (!component) {
224
219
  throw new Error(`Component not found: ${componentId}`);
225
220
  }
226
-
221
+
227
222
  const key = this.getComponentKey(component.Name, component.Namespace, component.Version, component.SourceRegistryID);
228
-
223
+
229
224
  // Check if already compiled and cached
230
225
  if (this.compiledComponentCache.has(key)) {
231
226
  const cached = this.compiledComponentCache.get(key)!;
232
227
  cached.lastUsed = new Date();
233
228
  cached.useCount++;
234
-
229
+
235
230
  // Track reference if provided
236
231
  if (referenceId) {
237
232
  this.addComponentReference(key, referenceId);
238
233
  }
239
-
234
+
240
235
  if (this.debug) {
241
236
  console.log(`✅ Reusing compiled component from cache: ${key} (use count: ${cached.useCount})`);
242
237
  }
243
238
  // Call the factory function to get the ComponentObject
244
239
  return cached.component(this.runtimeContext);
245
240
  }
246
-
241
+
247
242
  // Not in cache, need to load and compile
248
243
  if (this.debug) {
249
244
  console.log(`🔄 Loading and compiling component: ${key}`);
250
245
  }
251
-
246
+
252
247
  // Get the component specification
253
248
  const spec = await this.getComponentSpec(componentId, contextUser);
254
-
249
+
255
250
  // Compile the component
256
251
  // Load all libraries from metadata engine
257
252
  const allLibraries = this.componentEngine.ComponentLibraries || [];
258
-
253
+
259
254
  let compilationResult;
260
255
  try {
261
256
  compilationResult = await this.compiler.compile({
262
257
  componentName: component.Name,
263
258
  componentCode: spec.code,
264
259
  libraries: spec.libraries,
265
- allLibraries
260
+ allLibraries,
266
261
  });
267
- }
268
- catch (compileEx) {
262
+ } catch (compileEx) {
269
263
  // log then throw
270
- console.error(`🔴 Error compiling component ${component.Name}`,compileEx);
264
+ console.error(`🔴 Error compiling component ${component.Name}`, compileEx);
271
265
  throw compileEx;
272
266
  }
273
-
267
+
274
268
  if (!compilationResult.success) {
275
269
  console.error(`🔴 Error compiling component ${component.Name}`, compilationResult, 'Code', spec.code);
276
270
  throw new Error(`Failed to compile component ${component.Name}: ${compilationResult.error}`);
277
271
  }
278
-
272
+
279
273
  // Cache the compiled component
280
274
  const metadata: RegistryComponentMetadata = {
281
275
  name: component.Name,
@@ -290,9 +284,9 @@ export class ComponentRegistryService {
290
284
  libraries: spec.libraries,
291
285
  dependencies: spec.dependencies,
292
286
  sourceRegistryID: component.SourceRegistryID,
293
- isLocal: !component.SourceRegistryID
287
+ isLocal: !component.SourceRegistryID,
294
288
  };
295
-
289
+
296
290
  if (!compilationResult.component) {
297
291
  throw new Error(`Component compilation succeeded but no component returned`);
298
292
  }
@@ -302,18 +296,18 @@ export class ComponentRegistryService {
302
296
  metadata,
303
297
  compiledAt: new Date(),
304
298
  lastUsed: new Date(),
305
- useCount: 1
299
+ useCount: 1,
306
300
  });
307
-
301
+
308
302
  // Track reference
309
303
  if (referenceId) {
310
304
  this.addComponentReference(key, referenceId);
311
305
  }
312
-
306
+
313
307
  // Call the factory function to get the ComponentObject
314
308
  return compiledComponentFactory(this.runtimeContext);
315
309
  }
316
-
310
+
317
311
  /**
318
312
  * Get compiled component from external registry by registry name
319
313
  * This is used when spec.registry field is populated
@@ -327,45 +321,45 @@ export class ComponentRegistryService {
327
321
  contextUser?: UserInfo
328
322
  ): Promise<any> {
329
323
  await this.initialize(contextUser);
330
-
324
+
331
325
  if (this.debug) {
332
326
  console.log(`🌐 [ComponentRegistryService] Fetching from external registry: ${registryName}/${namespace}/${name}@${version}`);
333
327
  }
334
-
328
+
335
329
  // Find the registry by name in ComponentRegistries
336
- const registry = this.componentEngine.ComponentRegistries?.find(
337
- r => r.Name === registryName && r.Status === 'Active'
338
- );
339
-
330
+ const registry = this.componentEngine.ComponentRegistries?.find((r) => r.Name === registryName && r.Status === 'Active');
331
+
340
332
  if (!registry) {
341
333
  throw new Error(`Registry not found or inactive: ${registryName}`);
342
334
  }
343
-
335
+
344
336
  if (this.debug) {
345
337
  console.log(`✅ [ComponentRegistryService] Found registry: ${registry.Name} (ID: ${registry.ID})`);
346
338
  }
347
-
339
+
348
340
  // Get GraphQL client - use provided one or fallback to Metadata.Provider
349
341
  const graphQLClient = await this.getGraphQLClient();
350
342
  if (!graphQLClient) {
351
- throw new Error('GraphQL client not available for external registry fetching. No client provided and Metadata.Provider is not a GraphQLDataProvider.');
343
+ throw new Error(
344
+ 'GraphQL client not available for external registry fetching. No client provided and Metadata.Provider is not a GraphQLDataProvider.'
345
+ );
352
346
  }
353
-
347
+
354
348
  // Check if we have a cached version first
355
349
  const key = `external:${registryName}:${namespace}:${name}:${version}`;
356
350
  const cached = this.compiledComponentCache.get(key);
357
-
351
+
358
352
  try {
359
353
  // Fetch component spec from external registry via MJServer
360
354
  // Pass cached hash if available for efficient caching
361
355
  const spec = await graphQLClient.GetRegistryComponent({
362
- registryName: registry.Name, // Pass registry name, not ID
356
+ registryName: registry.Name, // Pass registry name, not ID
363
357
  namespace,
364
358
  name,
365
359
  version,
366
- hash: cached?.specHash // Pass cached hash if available
360
+ hash: cached?.specHash, // Pass cached hash if available
367
361
  });
368
-
362
+
369
363
  // If null returned, it means not modified (304)
370
364
  if (!spec && cached?.specHash) {
371
365
  if (this.debug) {
@@ -373,27 +367,27 @@ export class ComponentRegistryService {
373
367
  }
374
368
  cached.lastUsed = new Date();
375
369
  cached.useCount++;
376
-
370
+
377
371
  // Track reference
378
372
  if (referenceId) {
379
373
  this.addComponentReference(key, referenceId);
380
374
  }
381
-
375
+
382
376
  // Call the factory function to get the ComponentObject
383
377
  return cached.component(this.runtimeContext);
384
378
  }
385
-
379
+
386
380
  if (!spec) {
387
381
  throw new Error(`Component not found in registry ${registryName}: ${namespace}/${name}@${version}`);
388
382
  }
389
-
383
+
390
384
  if (this.debug) {
391
385
  console.log(`✅ [ComponentRegistryService] Fetched spec from external registry: ${spec.name}`);
392
386
  }
393
-
387
+
394
388
  // Calculate hash of the fetched spec
395
389
  const specHash = await this.calculateSpecHash(spec);
396
-
390
+
397
391
  // Check if hash matches cached version (shouldn't happen if server works correctly)
398
392
  if (cached && cached.specHash === specHash) {
399
393
  if (this.debug) {
@@ -401,35 +395,37 @@ export class ComponentRegistryService {
401
395
  }
402
396
  cached.lastUsed = new Date();
403
397
  cached.useCount++;
404
-
398
+
405
399
  // Track reference
406
400
  if (referenceId) {
407
401
  this.addComponentReference(key, referenceId);
408
402
  }
409
-
403
+
410
404
  // Call the factory function to get the ComponentObject
411
405
  return cached.component(this.runtimeContext);
412
406
  }
413
-
407
+
414
408
  // Spec has changed or is new, need to compile
415
409
  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)}...)`);
410
+ console.log(
411
+ `🔄 [ComponentRegistryService] Spec changed for: ${key}, recompiling (old hash: ${cached.specHash?.substring(0, 8)}..., new hash: ${specHash.substring(0, 8)}...)`
412
+ );
417
413
  }
418
-
414
+
419
415
  // Load all libraries from metadata engine
420
416
  const allLibraries = this.componentEngine.ComponentLibraries || [];
421
-
417
+
422
418
  // Compile the component
423
419
  const compilationResult = await this.compiler.compile({
424
420
  componentName: spec.name,
425
421
  componentCode: spec.code || '',
426
- allLibraries: allLibraries
422
+ allLibraries: allLibraries,
427
423
  });
428
-
424
+
429
425
  if (!compilationResult.success || !compilationResult.component) {
430
426
  throw new Error(`Failed to compile component: ${compilationResult.error?.message || 'Unknown error'}`);
431
427
  }
432
-
428
+
433
429
  // Cache the compiled component with spec hash
434
430
  this.compiledComponentCache.set(key, {
435
431
  component: compilationResult.component.factory,
@@ -439,23 +435,23 @@ export class ComponentRegistryService {
439
435
  version: spec.version || '1.0.0',
440
436
  description: spec.description || '',
441
437
  type: spec.type,
442
- isLocal: false // This is from an external registry
438
+ isLocal: false, // This is from an external registry
443
439
  },
444
440
  compiledAt: new Date(),
445
441
  lastUsed: new Date(),
446
442
  useCount: 1,
447
- specHash: specHash // Store the hash for future comparison
443
+ specHash: specHash, // Store the hash for future comparison
448
444
  });
449
-
445
+
450
446
  // Track reference
451
447
  if (referenceId) {
452
448
  this.addComponentReference(key, referenceId);
453
449
  }
454
-
450
+
455
451
  if (this.debug) {
456
452
  console.log(`🎯 [ComponentRegistryService] Successfully compiled external component: ${spec.name}`);
457
453
  }
458
-
454
+
459
455
  // Call the factory function to get the ComponentObject
460
456
  return compilationResult.component.factory(this.runtimeContext);
461
457
  } catch (error) {
@@ -463,21 +459,18 @@ export class ComponentRegistryService {
463
459
  throw error;
464
460
  }
465
461
  }
466
-
462
+
467
463
  /**
468
464
  * Get component specification from database or external registry
469
465
  */
470
- async getComponentSpec(
471
- componentId: string,
472
- contextUser?: UserInfo
473
- ): Promise<ComponentSpec> {
466
+ async getComponentSpec(componentId: string, contextUser?: UserInfo): Promise<ComponentSpec> {
474
467
  await this.initialize(contextUser);
475
-
468
+
476
469
  const component = this.componentEngine.Components.find((c: ComponentEntity) => c.ID === componentId);
477
470
  if (!component) {
478
471
  throw new Error(`Component not found: ${componentId}`);
479
472
  }
480
-
473
+
481
474
  if (!component.SourceRegistryID) {
482
475
  // LOCAL: Use specification from database
483
476
  if (!component.Specification) {
@@ -485,7 +478,7 @@ export class ComponentRegistryService {
485
478
  }
486
479
  return JSON.parse(component.Specification);
487
480
  }
488
-
481
+
489
482
  // EXTERNAL: Check if we have a cached version
490
483
  if (component.Specification && component.LastSyncedAt) {
491
484
  // For now, always use cached version (no expiration)
@@ -494,35 +487,33 @@ export class ComponentRegistryService {
494
487
  }
495
488
  return JSON.parse(component.Specification);
496
489
  }
497
-
490
+
498
491
  // Need to fetch from external registry
499
- const registry = this.componentEngine.ComponentRegistries?.find(
500
- r => r.ID === component.SourceRegistryID
501
- );
502
-
492
+ const registry = this.componentEngine.ComponentRegistries?.find((r) => r.ID === component.SourceRegistryID);
493
+
503
494
  if (!registry) {
504
495
  throw new Error(`Registry not found: ${component.SourceRegistryID}`);
505
496
  }
506
-
497
+
507
498
  // Try GraphQL client first if available
508
499
  let spec: ComponentSpec;
509
-
500
+
510
501
  if (this.graphQLClient) {
511
502
  if (this.debug) {
512
503
  console.log(`Fetching from registry via GraphQL: ${component.Name}`);
513
504
  }
514
-
505
+
515
506
  const result = await this.graphQLClient.GetRegistryComponent({
516
507
  registryName: registry.Name,
517
508
  namespace: component.Namespace || '',
518
509
  name: component.Name,
519
- version: component.Version
510
+ version: component.Version,
520
511
  });
521
-
512
+
522
513
  if (!result) {
523
514
  throw new Error(`Component not found in registry: ${component.Name}`);
524
515
  }
525
-
516
+
526
517
  spec = result;
527
518
  } else {
528
519
  // Fallback to direct HTTP if no GraphQL client
@@ -534,13 +525,13 @@ export class ComponentRegistryService {
534
525
  this.getRegistryApiKey(registry.ID)
535
526
  );
536
527
  }
537
-
528
+
538
529
  // Store in local database for future use
539
530
  await this.cacheExternalComponent(componentId, spec, contextUser);
540
-
531
+
541
532
  return spec;
542
533
  }
543
-
534
+
544
535
  /**
545
536
  * Fetch component from external registry via HTTP
546
537
  */
@@ -552,141 +543,132 @@ export class ComponentRegistryService {
552
543
  apiKey?: string
553
544
  ): Promise<ComponentSpec> {
554
545
  const url = `${uri}/components/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/${version}`;
555
-
546
+
556
547
  const headers: HeadersInit = {
557
- 'Accept': 'application/json'
548
+ Accept: 'application/json',
558
549
  };
559
-
550
+
560
551
  if (apiKey) {
561
552
  headers['Authorization'] = `Bearer ${apiKey}`;
562
553
  }
563
-
554
+
564
555
  if (this.debug) {
565
556
  console.log(`Fetching from external registry: ${url}`);
566
557
  }
567
-
558
+
568
559
  const response = await fetch(url, { headers });
569
-
560
+
570
561
  if (!response.ok) {
571
562
  throw new Error(`Registry fetch failed: ${response.status} ${response.statusText}`);
572
563
  }
573
-
574
- const spec = await response.json() as ComponentSpec;
564
+
565
+ const spec = (await response.json()) as ComponentSpec;
575
566
  return spec;
576
567
  }
577
-
568
+
578
569
  /**
579
570
  * Cache an external component in the local database
580
571
  */
581
- private async cacheExternalComponent(
582
- componentId: string,
583
- spec: ComponentSpec,
584
- contextUser?: UserInfo
585
- ): Promise<void> {
572
+ private async cacheExternalComponent(componentId: string, spec: ComponentSpec, contextUser?: UserInfo): Promise<void> {
586
573
  // Get the actual entity object to save
587
574
  const md = new Metadata();
588
575
  const componentEntity = await md.GetEntityObject<ComponentEntity>('MJ: Components', contextUser);
589
-
576
+
590
577
  // Load the existing component
591
- if (!await componentEntity.Load(componentId)) {
578
+ if (!(await componentEntity.Load(componentId))) {
592
579
  throw new Error(`Failed to load component entity: ${componentId}`);
593
580
  }
594
-
581
+
595
582
  // Update with fetched specification and all fields from spec
596
583
  componentEntity.Specification = JSON.stringify(spec);
597
584
  componentEntity.LastSyncedAt = new Date();
598
-
585
+
599
586
  // Set ReplicatedAt only on first fetch
600
587
  if (!componentEntity.ReplicatedAt) {
601
588
  componentEntity.ReplicatedAt = new Date();
602
589
  }
603
-
590
+
604
591
  // Update all fields from the spec with strong typing
605
592
  if (spec.name) {
606
593
  componentEntity.Name = spec.name;
607
594
  }
608
-
595
+
609
596
  if (spec.namespace) {
610
597
  componentEntity.Namespace = spec.namespace;
611
598
  }
612
-
599
+
613
600
  if (spec.version) {
614
601
  componentEntity.Version = spec.version;
615
602
  }
616
-
603
+
617
604
  if (spec.title) {
618
605
  componentEntity.Title = spec.title;
619
606
  }
620
-
607
+
621
608
  if (spec.description) {
622
609
  componentEntity.Description = spec.description;
623
610
  }
624
-
611
+
625
612
  if (spec.type) {
626
613
  // Map spec type to entity type (entity has specific enum values)
627
614
  const typeMap: Record<string, ComponentEntity['Type']> = {
628
- 'report': 'Report',
629
- 'dashboard': 'Dashboard',
630
- 'form': 'Form',
631
- 'table': 'Table',
632
- 'chart': 'Chart',
633
- 'navigation': 'Navigation',
634
- 'search': 'Search',
635
- 'widget': 'Widget',
636
- 'utility': 'Utility',
637
- 'other': 'Other'
615
+ report: 'Report',
616
+ dashboard: 'Dashboard',
617
+ form: 'Form',
618
+ table: 'Table',
619
+ chart: 'Chart',
620
+ navigation: 'Navigation',
621
+ search: 'Search',
622
+ widget: 'Widget',
623
+ utility: 'Utility',
624
+ other: 'Other',
638
625
  };
639
-
626
+
640
627
  const mappedType = typeMap[spec.type.toLowerCase()];
641
628
  if (mappedType) {
642
629
  componentEntity.Type = mappedType;
643
630
  }
644
631
  }
645
-
632
+
646
633
  if (spec.functionalRequirements) {
647
634
  componentEntity.FunctionalRequirements = spec.functionalRequirements;
648
635
  }
649
-
636
+
650
637
  if (spec.technicalDesign) {
651
638
  componentEntity.TechnicalDesign = spec.technicalDesign;
652
639
  }
653
-
640
+
654
641
  // Save back to database
655
642
  const result = await componentEntity.Save();
656
643
  if (!result) {
657
- throw new Error(`Failed to save cached component: ${componentEntity.Name}\n${componentEntity.LatestResult.Message || componentEntity.LatestResult.Error || componentEntity.LatestResult.Errors?.join(',')}`);
644
+ throw new Error(
645
+ `Failed to save cached component: ${componentEntity.Name}\n${componentEntity.LatestResult.Message || componentEntity.LatestResult.Error || componentEntity.LatestResult.Errors?.join(',')}`
646
+ );
658
647
  }
659
-
648
+
660
649
  if (this.debug) {
661
650
  console.log(`Cached external component: ${componentEntity.Name} at ${componentEntity.LastSyncedAt}`);
662
651
  }
663
-
652
+
664
653
  // Refresh metadata cache after saving
665
654
  await this.componentEngine.Config(true, contextUser);
666
655
  }
667
-
656
+
668
657
  /**
669
658
  * Load component dependencies from database
670
659
  */
671
- async loadDependencies(
672
- componentId: string,
673
- contextUser?: UserInfo
674
- ): Promise<ComponentDependencyInfo[]> {
660
+ async loadDependencies(componentId: string, contextUser?: UserInfo): Promise<ComponentDependencyInfo[]> {
675
661
  await this.initialize(contextUser);
676
-
662
+
677
663
  // Get dependencies from metadata cache
678
- const dependencies = this.componentEngine.ComponentDependencies?.filter(
679
- d => d.ComponentID === componentId
680
- ) || [];
681
-
664
+ const dependencies = this.componentEngine.ComponentDependencies?.filter((d) => d.ComponentID === componentId) || [];
665
+
682
666
  const result: ComponentDependencyInfo[] = [];
683
-
667
+
684
668
  for (const dep of dependencies) {
685
669
  // Find the dependency component
686
- const depComponent = this.componentEngine.Components.find(
687
- (c: ComponentEntity) => c.ID === dep.DependencyComponentID
688
- );
689
-
670
+ const depComponent = this.componentEngine.Components.find((c: ComponentEntity) => c.ID === dep.DependencyComponentID);
671
+
690
672
  if (depComponent) {
691
673
  result.push({
692
674
  name: depComponent.Name,
@@ -694,78 +676,68 @@ export class ComponentRegistryService {
694
676
  version: depComponent.Version, // Version comes from the linked Component record
695
677
  isRequired: true, // All dependencies are required in MemberJunction
696
678
  location: depComponent.SourceRegistryID ? 'registry' : 'embedded',
697
- sourceRegistryID: depComponent.SourceRegistryID
679
+ sourceRegistryID: depComponent.SourceRegistryID,
698
680
  });
699
681
  }
700
682
  }
701
-
683
+
702
684
  return result;
703
685
  }
704
-
686
+
705
687
  /**
706
688
  * Resolve full dependency tree for a component
707
689
  */
708
- async resolveDependencyTree(
709
- componentId: string,
710
- contextUser?: UserInfo,
711
- visited = new Set<string>()
712
- ): Promise<DependencyTree> {
690
+ async resolveDependencyTree(componentId: string, contextUser?: UserInfo, visited = new Set<string>()): Promise<DependencyTree> {
713
691
  if (visited.has(componentId)) {
714
- return {
715
- componentId,
716
- circular: true
692
+ return {
693
+ componentId,
694
+ circular: true,
717
695
  };
718
696
  }
719
697
  visited.add(componentId);
720
-
698
+
721
699
  await this.initialize(contextUser);
722
-
700
+
723
701
  const component = this.componentEngine.Components.find((c: ComponentEntity) => c.ID === componentId);
724
702
  if (!component) {
725
703
  return { componentId, dependencies: [] };
726
704
  }
727
-
705
+
728
706
  // Get direct dependencies
729
707
  const directDeps = await this.loadDependencies(componentId, contextUser);
730
-
708
+
731
709
  // Recursively resolve each dependency
732
710
  const dependencies: DependencyTree[] = [];
733
711
  for (const dep of directDeps) {
734
712
  // Find the dependency component
735
713
  const depComponent = this.componentEngine.Components.find(
736
- c => c.Name.trim().toLowerCase() === dep.name.trim().toLowerCase() &&
737
- c.Namespace?.trim().toLowerCase() === dep.namespace?.trim().toLowerCase()
714
+ (c) =>
715
+ c.Name.trim().toLowerCase() === dep.name.trim().toLowerCase() &&
716
+ c.Namespace?.trim().toLowerCase() === dep.namespace?.trim().toLowerCase()
738
717
  );
739
-
718
+
740
719
  if (depComponent) {
741
- const subTree = await this.resolveDependencyTree(
742
- depComponent.ID,
743
- contextUser,
744
- visited
745
- );
720
+ const subTree = await this.resolveDependencyTree(depComponent.ID, contextUser, visited);
746
721
  dependencies.push(subTree);
747
722
  }
748
723
  }
749
-
724
+
750
725
  return {
751
726
  componentId,
752
727
  name: component.Name,
753
728
  namespace: component.Namespace || undefined,
754
729
  version: component.Version,
755
730
  dependencies,
756
- totalCount: dependencies.reduce((sum, d) => sum + (d.totalCount || 1), 1)
731
+ totalCount: dependencies.reduce((sum, d) => sum + (d.totalCount || 1), 1),
757
732
  };
758
733
  }
759
-
734
+
760
735
  /**
761
736
  * Get components to load in dependency order
762
737
  */
763
- async getComponentsToLoad(
764
- rootComponentId: string,
765
- contextUser?: UserInfo
766
- ): Promise<string[]> {
738
+ async getComponentsToLoad(rootComponentId: string, contextUser?: UserInfo): Promise<string[]> {
767
739
  const tree = await this.resolveDependencyTree(rootComponentId, contextUser);
768
-
740
+
769
741
  // Flatten tree in dependency order (depth-first)
770
742
  const ordered: string[] = [];
771
743
  const processNode = (node: DependencyTree) => {
@@ -777,10 +749,10 @@ export class ComponentRegistryService {
777
749
  }
778
750
  };
779
751
  processNode(tree);
780
-
752
+
781
753
  return ordered;
782
754
  }
783
-
755
+
784
756
  /**
785
757
  * Add a reference to a component
786
758
  */
@@ -790,7 +762,7 @@ export class ComponentRegistryService {
790
762
  }
791
763
  this.componentReferences.get(componentKey)!.add(referenceId);
792
764
  }
793
-
765
+
794
766
  /**
795
767
  * Remove a reference to a component
796
768
  */
@@ -798,14 +770,14 @@ export class ComponentRegistryService {
798
770
  const refs = this.componentReferences.get(componentKey);
799
771
  if (refs) {
800
772
  refs.delete(referenceId);
801
-
773
+
802
774
  // If no more references and cache cleanup is enabled
803
775
  if (refs.size === 0) {
804
776
  this.considerCacheEviction(componentKey);
805
777
  }
806
778
  }
807
779
  }
808
-
780
+
809
781
  /**
810
782
  * Consider evicting a component from cache
811
783
  */
@@ -814,7 +786,7 @@ export class ComponentRegistryService {
814
786
  if (cached) {
815
787
  const timeSinceLastUse = Date.now() - cached.lastUsed.getTime();
816
788
  const evictionThreshold = 5 * 60 * 1000; // 5 minutes
817
-
789
+
818
790
  if (timeSinceLastUse > evictionThreshold) {
819
791
  if (this.debug) {
820
792
  console.log(`🗑️ Evicting unused component from cache: ${componentKey}`);
@@ -823,7 +795,7 @@ export class ComponentRegistryService {
823
795
  }
824
796
  }
825
797
  }
826
-
798
+
827
799
  /**
828
800
  * Get API key for a registry from secure configuration
829
801
  * @param registryId - Registry ID
@@ -836,7 +808,7 @@ export class ComponentRegistryService {
836
808
  const envKey = `REGISTRY_API_KEY_${registryId.replace(/-/g, '_').toUpperCase()}`;
837
809
  return process.env[envKey];
838
810
  }
839
-
811
+
840
812
  /**
841
813
  * Get cache statistics
842
814
  */
@@ -846,17 +818,17 @@ export class ComponentRegistryService {
846
818
  memoryEstimate: string;
847
819
  } {
848
820
  let totalUseCount = 0;
849
- this.compiledComponentCache.forEach(cached => {
821
+ this.compiledComponentCache.forEach((cached) => {
850
822
  totalUseCount += cached.useCount;
851
823
  });
852
-
824
+
853
825
  return {
854
826
  compiledComponents: this.compiledComponentCache.size,
855
827
  totalUseCount,
856
- memoryEstimate: `~${(this.compiledComponentCache.size * 50)}KB` // Rough estimate
828
+ memoryEstimate: `~${this.compiledComponentCache.size * 50}KB`, // Rough estimate
857
829
  };
858
830
  }
859
-
831
+
860
832
  /**
861
833
  * Clear all caches
862
834
  */
@@ -888,18 +860,18 @@ export class ComponentRegistryService {
888
860
  ComponentRegistryService.instance = null;
889
861
  }
890
862
  }
891
-
863
+
892
864
  /**
893
865
  * Generate a cache key for a component
894
866
  */
895
867
  private getComponentKey(
896
- name: string,
897
- namespace: string | null | undefined,
898
- version: string,
868
+ name: string,
869
+ namespace: string | null | undefined,
870
+ version: string,
899
871
  sourceRegistryId: string | null | undefined
900
872
  ): string {
901
873
  const registryPart = sourceRegistryId || 'local';
902
874
  const namespacePart = namespace || 'global';
903
875
  return `${registryPart}/${namespacePart}/${name}@${version}`;
904
876
  }
905
- }
877
+ }