@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.
- package/.turbo/turbo-build.log +15 -20
- package/CHANGELOG.md +26 -0
- package/README.md +171 -1
- package/dist/compiler/component-compiler.d.ts.map +1 -1
- package/dist/compiler/component-compiler.js +59 -8
- package/dist/compiler/component-compiler.js.map +1 -1
- package/dist/component-manager/component-manager.d.ts +39 -0
- package/dist/component-manager/component-manager.d.ts.map +1 -0
- package/dist/component-manager/component-manager.js +474 -0
- package/dist/component-manager/component-manager.js.map +1 -0
- package/dist/component-manager/index.d.ts +3 -0
- package/dist/component-manager/index.d.ts.map +1 -0
- package/dist/component-manager/index.js +6 -0
- package/dist/component-manager/index.js.map +1 -0
- package/dist/component-manager/types.d.ts +62 -0
- package/dist/component-manager/types.d.ts.map +1 -0
- package/dist/component-manager/types.js +3 -0
- package/dist/component-manager/types.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/registry/component-registry-service.d.ts +16 -1
- package/dist/registry/component-registry-service.d.ts.map +1 -1
- package/dist/registry/component-registry-service.js +212 -10
- package/dist/registry/component-registry-service.js.map +1 -1
- package/dist/registry/component-registry.d.ts +1 -1
- package/dist/registry/component-registry.d.ts.map +1 -1
- package/dist/registry/component-registry.js.map +1 -1
- package/dist/registry/component-resolver.d.ts.map +1 -1
- package/dist/registry/component-resolver.js +122 -52
- package/dist/registry/component-resolver.js.map +1 -1
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js.map +1 -1
- package/dist/runtime/component-hierarchy.d.ts +4 -0
- package/dist/runtime/component-hierarchy.d.ts.map +1 -1
- package/dist/runtime/component-hierarchy.js +127 -12
- package/dist/runtime/component-hierarchy.js.map +1 -1
- package/dist/runtime.umd.js +535 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utilities/component-unwrapper.d.ts +7 -0
- package/dist/utilities/component-unwrapper.d.ts.map +1 -0
- package/dist/utilities/component-unwrapper.js +369 -0
- package/dist/utilities/component-unwrapper.js.map +1 -0
- package/dist/utilities/index.d.ts +1 -0
- package/dist/utilities/index.d.ts.map +1 -1
- package/dist/utilities/index.js +1 -0
- package/dist/utilities/index.js.map +1 -1
- package/dist/utilities/library-loader.d.ts +3 -0
- package/dist/utilities/library-loader.d.ts.map +1 -1
- package/dist/utilities/library-loader.js +101 -17
- package/dist/utilities/library-loader.js.map +1 -1
- package/examples/component-registry-integration.ts +191 -0
- package/package.json +6 -5
- package/src/compiler/component-compiler.ts +101 -23
- package/src/component-manager/component-manager.ts +736 -0
- package/src/component-manager/index.ts +13 -0
- package/src/component-manager/types.ts +204 -0
- package/src/index.ts +37 -1
- package/src/registry/component-registry-service.ts +315 -18
- package/src/registry/component-registry.ts +1 -1
- package/src/registry/component-resolver.ts +159 -67
- package/src/registry/index.ts +1 -1
- package/src/runtime/component-hierarchy.ts +124 -13
- package/src/types/index.ts +2 -0
- package/src/utilities/component-unwrapper.ts +481 -0
- package/src/utilities/index.ts +2 -1
- 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
|
-
|
|
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
|
|
299
|
+
const compiledComponentFactory = compilationResult.component.factory;
|
|
173
300
|
this.compiledComponentCache.set(key, {
|
|
174
|
-
component:
|
|
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
|
-
|
|
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
|
|
230
|
-
|
|
231
|
-
}
|
|
507
|
+
// Try GraphQL client first if available
|
|
508
|
+
let spec: ComponentSpec;
|
|
232
509
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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);
|