@memberjunction/react-runtime 5.33.0 → 5.34.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/react-runtime",
3
- "version": "5.33.0",
3
+ "version": "5.34.1",
4
4
  "description": "Platform-agnostic React component runtime for MemberJunction. Provides core compilation, registry, and execution capabilities for React components in any JavaScript environment.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,11 +29,11 @@
29
29
  },
30
30
  "homepage": "https://github.com/MemberJunction/MJ#readme",
31
31
  "dependencies": {
32
- "@memberjunction/core": "5.33.0",
33
- "@memberjunction/global": "5.33.0",
34
- "@memberjunction/interactive-component-types": "5.33.0",
35
- "@memberjunction/core-entities": "5.33.0",
36
- "@memberjunction/graphql-dataprovider": "5.33.0",
32
+ "@memberjunction/core": "5.34.1",
33
+ "@memberjunction/global": "5.34.1",
34
+ "@memberjunction/interactive-component-types": "5.34.1",
35
+ "@memberjunction/core-entities": "5.34.1",
36
+ "@memberjunction/graphql-dataprovider": "5.34.1",
37
37
  "@babel/standalone": "^7.29.1",
38
38
  "rxjs": "^7.8.2"
39
39
  },
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { ComponentSpec, ComponentLibraryDependency } from '@memberjunction/interactive-component-types';
7
7
  import { UserInfo, Metadata, LogError } from '@memberjunction/core';
8
- import { ComponentMetadataEngine, MJComponentLibraryEntity, MJComponentEntityExtended } from '@memberjunction/core-entities';
8
+ import { ComponentMetadataEngine, MJComponentLibraryEntity } from '@memberjunction/core-entities';
9
9
 
10
10
  import { ComponentCompiler } from '../compiler';
11
11
  import { ComponentRegistry } from '../registry';
@@ -424,12 +424,7 @@ export class ComponentManager {
424
424
  }
425
425
  }
426
426
 
427
- // Load dependencies
428
- // Prefer the input spec's dependencies over the cached result's dependencies.
429
- // When a parent component is served from cache, result.spec contains stale
430
- // dependency code. The input spec has the latest dependency code from the caller.
431
- // Each dependency still gets its own individual cache check via loadComponent.
432
- const dependencies = spec.dependencies || result.spec?.dependencies;
427
+ const dependencies = this.resolveDependencies(spec.dependencies, result.spec?.dependencies);
433
428
  if (dependencies) {
434
429
  for (const dep of dependencies) {
435
430
  // Normalize dependency spec for local registry lookup
@@ -489,6 +484,29 @@ export class ComponentManager {
489
484
  return results;
490
485
  }
491
486
 
487
+ /**
488
+ * Determines which dependency list to use when loading a component's children.
489
+ *
490
+ * - Input deps with code take priority (caller has the latest version).
491
+ * - When input has no deps at all, fall back to fetched/cached deps
492
+ * (e.g., a registry reference spec with no dependency list).
493
+ * - When input has only stubs (no code), prefer fetched deps if they
494
+ * contain code (registry-populated hierarchies from Skip where the
495
+ * full tree is returned in one response).
496
+ */
497
+ private resolveDependencies(
498
+ inputDeps: ComponentSpec[] | undefined,
499
+ fetchedDeps: ComponentSpec[] | undefined
500
+ ): ComponentSpec[] | undefined {
501
+ const inputHasDeps = inputDeps && inputDeps.length > 0;
502
+ const inputHasCode = inputHasDeps && inputDeps.some(d => !!d.code);
503
+
504
+ if (inputHasCode) return inputDeps;
505
+ if (!inputHasDeps) return fetchedDeps || inputDeps;
506
+ if (fetchedDeps?.some(d => !!d.code)) return fetchedDeps;
507
+ return inputDeps;
508
+ }
509
+
492
510
  /**
493
511
  * Check if a component needs to be fetched from a registry
494
512
  */
@@ -515,87 +533,82 @@ export class ComponentManager {
515
533
  // Check cache first
516
534
  const cacheKey = this.getComponentKey(spec, {});
517
535
  const cached = this.fetchCache.get(cacheKey);
518
-
536
+
519
537
  if (cached && this.isCacheValid(cached)) {
520
538
  this.log(`Using cached spec for: ${spec.name}`);
521
539
  return cached.spec;
522
540
  }
523
-
541
+
524
542
  // Handle LOCAL registry components (registry is null/undefined)
525
543
  if (!spec.registry) {
526
- this.log(`Fetching from local registry: ${spec.name}`);
527
-
528
- // Find component in local ComponentMetadataEngine
529
- const localComponent = this.componentEngine.Components?.find(
530
- (c: MJComponentEntityExtended) => {
531
- // Match by name (case-insensitive for better compatibility)
532
- const nameMatch = c.Name?.toLowerCase() === spec.name?.toLowerCase();
533
-
534
- // Match by namespace if provided (handle different formats)
535
- const namespaceMatch = !spec.namespace || c.Namespace?.toLowerCase() === spec.namespace?.toLowerCase();
544
+ const localComponent = await this.componentEngine.FindComponent(spec.name, spec.namespace);
536
545
 
537
- if (nameMatch && !namespaceMatch) {
538
- }
539
-
540
- return nameMatch && namespaceMatch;
541
- }
542
- );
543
-
544
546
  if (!localComponent) {
545
547
  throw new Error(`Local component not found: ${spec.name}`);
546
548
  }
547
-
548
- // Parse specification from local component
549
+
549
550
  if (!localComponent.Specification) {
550
551
  throw new Error(`Local component ${spec.name} has no specification`);
551
552
  }
552
-
553
+
553
554
  const fullSpec = JSON.parse(localComponent.Specification);
554
-
555
- // Cache it
555
+
556
556
  this.fetchCache.set(cacheKey, {
557
557
  spec: fullSpec,
558
558
  fetchedAt: new Date(),
559
559
  usageNotified: false
560
560
  });
561
-
561
+
562
562
  return fullSpec;
563
563
  }
564
-
564
+
565
565
  // Handle EXTERNAL registry components (registry has a name)
566
- // Initialize GraphQL client if needed
567
566
  if (!this.graphQLClient) {
568
567
  await this.initializeGraphQLClient();
569
568
  }
570
-
569
+
571
570
  if (!this.graphQLClient) {
572
571
  throw new Error('GraphQL client not available for registry fetching');
573
572
  }
574
-
575
- // Fetch from external registry
573
+
574
+ // Fetch from external registry, passing the cached hash (if any) so the
575
+ // server can return 304 Not Modified when the spec hasn't changed
576
576
  this.log(`Fetching from external registry: ${spec.registry}/${spec.name}`);
577
-
578
- const fullSpec = await this.graphQLClient.GetRegistryComponent({
577
+ const cachedHash = cached?.hash;
578
+
579
+ const response = await this.graphQLClient.GetRegistryComponentWithHash({
579
580
  registryName: spec.registry,
580
581
  namespace: spec.namespace || 'Global',
581
582
  name: spec.name,
582
- version: spec.version || 'latest'
583
+ version: spec.version || 'latest',
584
+ hash: cachedHash
583
585
  });
584
-
585
- if (!fullSpec) {
586
+
587
+ // If not modified (304), reuse the cached spec and refresh the TTL
588
+ if (response.notModified && cached) {
589
+ this.log(`Registry returned 304 for ${spec.name}, reusing cached spec`);
590
+ this.fetchCache.set(cacheKey, {
591
+ ...cached,
592
+ fetchedAt: new Date()
593
+ });
594
+ return cached.spec;
595
+ }
596
+
597
+ if (!response.specification) {
586
598
  throw new Error(`Component not found in registry: ${spec.registry}/${spec.name}`);
587
599
  }
588
-
589
- // Apply resolution mode if specified
600
+
601
+ const fullSpec = response.specification as ComponentSpec;
590
602
  const processedSpec = this.applyResolutionMode(fullSpec, spec, options?.resolutionMode);
591
-
592
- // Cache it
603
+
604
+ // Cache it with the registry hash for future 304 checks
593
605
  this.fetchCache.set(cacheKey, {
594
606
  spec: processedSpec,
595
607
  fetchedAt: new Date(),
608
+ hash: response.hash,
596
609
  usageNotified: false
597
610
  });
598
-
611
+
599
612
  return processedSpec;
600
613
  }
601
614
 
@@ -232,8 +232,8 @@ export class ComponentRegistryService {
232
232
  ): Promise<ComponentObject> {
233
233
  await this.initialize(contextUser);
234
234
 
235
- // Find component in metadata
236
- const component = this.componentEngine.Components.find((c: MJComponentEntity) => UUIDsEqual(c.ID, componentId));
235
+ // Find component in metadata via targeted query
236
+ const component = await this.componentEngine.FindComponentByID(componentId, contextUser);
237
237
  if (!component) {
238
238
  throw new Error(`Component not found: ${componentId}`);
239
239
  }
@@ -487,7 +487,7 @@ export class ComponentRegistryService {
487
487
  ): Promise<ComponentSpec> {
488
488
  await this.initialize(contextUser);
489
489
 
490
- const component = this.componentEngine.Components.find((c: MJComponentEntity) => UUIDsEqual(c.ID, componentId));
490
+ const component = await this.componentEngine.FindComponentByID(componentId, contextUser);
491
491
  if (!component) {
492
492
  throw new Error(`Component not found: ${componentId}`);
493
493
  }
@@ -697,16 +697,14 @@ export class ComponentRegistryService {
697
697
 
698
698
  for (const dep of dependencies) {
699
699
  // Find the dependency component
700
- const depComponent = this.componentEngine.Components.find(
701
- (c: MJComponentEntity) => UUIDsEqual(c.ID, dep.DependencyComponentID)
702
- );
703
-
700
+ const depComponent = await this.componentEngine.FindComponentByID(dep.DependencyComponentID, contextUser);
701
+
704
702
  if (depComponent) {
705
703
  result.push({
706
704
  name: depComponent.Name,
707
705
  namespace: depComponent.Namespace || '',
708
- version: depComponent.Version, // Version comes from the linked Component record
709
- isRequired: true, // All dependencies are required in MemberJunction
706
+ version: depComponent.Version,
707
+ isRequired: true,
710
708
  location: depComponent.SourceRegistryID ? 'registry' : 'embedded',
711
709
  sourceRegistryID: depComponent.SourceRegistryID
712
710
  });
@@ -734,27 +732,23 @@ export class ComponentRegistryService {
734
732
 
735
733
  await this.initialize(contextUser);
736
734
 
737
- const component = this.componentEngine.Components.find((c: MJComponentEntity) => UUIDsEqual(c.ID, componentId));
735
+ const component = await this.componentEngine.FindComponentByID(componentId, contextUser);
738
736
  if (!component) {
739
737
  return { componentId, dependencies: [] };
740
738
  }
741
-
739
+
742
740
  // Get direct dependencies
743
741
  const directDeps = await this.loadDependencies(componentId, contextUser);
744
-
742
+
745
743
  // Recursively resolve each dependency
746
744
  const dependencies: DependencyTree[] = [];
747
745
  for (const dep of directDeps) {
748
- // Find the dependency component
749
- const depComponent = this.componentEngine.Components.find(
750
- c => c.Name.trim().toLowerCase() === dep.name.trim().toLowerCase() &&
751
- c.Namespace?.trim().toLowerCase() === dep.namespace?.trim().toLowerCase()
752
- );
753
-
746
+ const depComponent = await this.componentEngine.FindComponent(dep.name, dep.namespace, undefined, contextUser);
747
+
754
748
  if (depComponent) {
755
749
  const subTree = await this.resolveDependencyTree(
756
- depComponent.ID,
757
- contextUser,
750
+ depComponent.ID,
751
+ contextUser,
758
752
  visited
759
753
  );
760
754
  dependencies.push(subTree);
@@ -95,7 +95,7 @@ export class ComponentResolver {
95
95
  }
96
96
  await this.componentEngine.Config(false, contextUser);
97
97
  if (this.debug) {
98
- console.log(`✅ [ComponentResolver] Component engine initialized with ${this.componentEngine.Components?.length || 0} components`);
98
+ console.log(`✅ [ComponentResolver] Component engine initialized`);
99
99
  }
100
100
  }
101
101
 
@@ -243,36 +243,18 @@ export class ComponentResolver {
243
243
  console.error(`❌ [ComponentResolver] Failed to fetch from external registry: ${spec.name} from ${spec.registry}`);
244
244
  }
245
245
  } else {
246
- // LOCAL REGISTRY: Get from local database
246
+ // LOCAL REGISTRY: Get from local database via targeted RunView
247
247
  if (this.debug) {
248
- console.log(`💾 [ComponentResolver] Looking for locally registered component`);
248
+ console.log(`💾 [ComponentResolver] Looking for locally registered component: ${spec.name}`);
249
249
  }
250
-
251
- // First, try to find the component in the metadata engine
252
- const allComponents = this.componentEngine.Components || [];
253
- if (this.debug) {
254
- console.log(`📊 [ComponentResolver] Total components in engine: ${allComponents.length}`);
255
- }
256
-
257
- // Log all matching names to see duplicates
258
- const matchingNames = allComponents.filter((c: any) => c.Name === spec.name);
259
- if (matchingNames.length > 0 && this.debug) {
260
- console.log(`🔎 [ComponentResolver] Found ${matchingNames.length} components with name "${spec.name}":`,
261
- matchingNames.map((c: any) => ({
262
- ID: c.ID,
263
- Name: c.Name,
264
- Namespace: c.Namespace,
265
- Version: c.Version,
266
- Status: c.Status
267
- }))
268
- );
269
- }
270
-
271
- const component = this.componentEngine.Components?.find(
272
- (c: any) => c.Name === spec.name &&
273
- c.Namespace === (spec.namespace || namespace)
250
+
251
+ const component = await this.componentEngine.FindComponent(
252
+ spec.name,
253
+ spec.namespace || namespace,
254
+ undefined,
255
+ contextUser
274
256
  );
275
-
257
+
276
258
  if (component) {
277
259
  if (this.debug) {
278
260
  console.log(`✅ [ComponentResolver] Found component in local DB:`, {
@@ -282,7 +264,7 @@ export class ComponentResolver {
282
264
  Version: component.Version
283
265
  });
284
266
  }
285
-
267
+
286
268
  // Get compiled component from registry service (local compilation)
287
269
  const compiledComponent = await this.registryService.getCompiledComponent(
288
270
  component.ID,
@@ -295,9 +277,6 @@ export class ComponentResolver {
295
277
  }
296
278
  } else {
297
279
  console.error(`❌ [ComponentResolver] Local registry component NOT found in database: ${spec.name} with namespace: ${spec.namespace || namespace}`);
298
- if (this.debug) {
299
- console.warn(`Local registry component not found in database: ${spec.name}`);
300
- }
301
280
  }
302
281
  }
303
282
  } catch (error) {