@memberjunction/react-runtime 2.111.0 → 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.
- package/.turbo/turbo-build.log +10 -11
- package/CHANGELOG.md +6 -6
- package/dist/compiler/component-compiler.d.ts.map +1 -1
- package/dist/compiler/component-compiler.js +66 -56
- package/dist/compiler/component-compiler.js.map +1 -1
- package/dist/component-manager/component-manager.d.ts.map +1 -1
- package/dist/component-manager/component-manager.js +48 -42
- package/dist/component-manager/component-manager.js.map +1 -1
- package/dist/component-manager/types.d.ts +1 -1
- package/dist/component-manager/types.d.ts.map +1 -1
- package/dist/component-manager/types.js.map +1 -1
- package/dist/registry/component-registry-service.d.ts +1 -1
- package/dist/registry/component-registry-service.d.ts.map +1 -1
- package/dist/registry/component-registry-service.js +34 -34
- package/dist/registry/component-registry-service.js.map +1 -1
- package/dist/registry/component-resolver.d.ts +1 -1
- package/dist/registry/component-resolver.d.ts.map +1 -1
- package/dist/registry/component-resolver.js +10 -11
- package/dist/registry/component-resolver.js.map +1 -1
- package/dist/runtime/component-hierarchy.d.ts +1 -1
- package/dist/runtime/component-hierarchy.d.ts.map +1 -1
- package/dist/runtime/component-hierarchy.js +43 -43
- package/dist/runtime/component-hierarchy.js.map +1 -1
- package/dist/runtime/prop-builder.d.ts.map +1 -1
- package/dist/runtime/prop-builder.js +22 -24
- package/dist/runtime/prop-builder.js.map +1 -1
- package/dist/runtime.umd.js +494 -511
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utilities/library-registry.d.ts +1 -1
- package/dist/utilities/library-registry.d.ts.map +1 -1
- package/dist/utilities/library-registry.js +10 -10
- package/dist/utilities/library-registry.js.map +1 -1
- package/package.json +5 -6
- package/src/compiler/component-compiler.ts +186 -164
- package/src/component-manager/component-manager.ts +162 -216
- package/src/component-manager/types.ts +27 -27
- package/src/registry/component-registry-service.ts +190 -218
- package/src/registry/component-resolver.ts +87 -98
- package/src/runtime/component-hierarchy.ts +57 -94
- package/src/runtime/prop-builder.ts +28 -33
- package/src/types/index.ts +3 -4
- package/src/utilities/library-registry.ts +14 -19
|
@@ -4,20 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { ComponentSpec, ComponentLibraryDependency } from '@memberjunction/interactive-component-types';
|
|
7
|
-
import { UserInfo, Metadata, LogError } from '@memberjunction/
|
|
7
|
+
import { UserInfo, Metadata, LogError } from '@memberjunction/global';
|
|
8
8
|
import { ComponentMetadataEngine, ComponentLibraryEntity, ComponentEntityExtended } from '@memberjunction/core-entities';
|
|
9
9
|
|
|
10
10
|
import { ComponentCompiler } from '../compiler';
|
|
11
11
|
import { ComponentRegistry } from '../registry';
|
|
12
12
|
import { RuntimeContext, ComponentObject } from '../types';
|
|
13
|
-
import {
|
|
14
|
-
LoadOptions,
|
|
15
|
-
LoadResult,
|
|
16
|
-
HierarchyResult,
|
|
17
|
-
ComponentManagerConfig,
|
|
18
|
-
CacheEntry,
|
|
19
|
-
ResolutionMode
|
|
20
|
-
} from './types';
|
|
13
|
+
import { LoadOptions, LoadResult, HierarchyResult, ComponentManagerConfig, CacheEntry, ResolutionMode } from './types';
|
|
21
14
|
|
|
22
15
|
/**
|
|
23
16
|
* Unified component management system that handles all component operations
|
|
@@ -28,16 +21,16 @@ export class ComponentManager {
|
|
|
28
21
|
private registry: ComponentRegistry;
|
|
29
22
|
private runtimeContext: RuntimeContext;
|
|
30
23
|
private config: ComponentManagerConfig;
|
|
31
|
-
|
|
24
|
+
|
|
32
25
|
// Caching
|
|
33
26
|
private fetchCache: Map<string, CacheEntry> = new Map();
|
|
34
27
|
private registryNotifications: Set<string> = new Set();
|
|
35
28
|
private loadingPromises: Map<string, Promise<LoadResult>> = new Map();
|
|
36
|
-
|
|
29
|
+
|
|
37
30
|
// Metadata engine
|
|
38
31
|
private componentEngine = ComponentMetadataEngine.Instance;
|
|
39
32
|
private graphQLClient: any = null;
|
|
40
|
-
|
|
33
|
+
|
|
41
34
|
constructor(
|
|
42
35
|
compiler: ComponentCompiler,
|
|
43
36
|
registry: ComponentRegistry,
|
|
@@ -54,44 +47,41 @@ export class ComponentManager {
|
|
|
54
47
|
enableUsageTracking: true,
|
|
55
48
|
dependencyBatchSize: 5,
|
|
56
49
|
fetchTimeout: 30000,
|
|
57
|
-
...config
|
|
50
|
+
...config,
|
|
58
51
|
};
|
|
59
|
-
|
|
52
|
+
|
|
60
53
|
this.log('ComponentManager initialized', {
|
|
61
54
|
debug: this.config.debug,
|
|
62
55
|
cacheTTL: this.config.cacheTTL,
|
|
63
|
-
usageTracking: this.config.enableUsageTracking
|
|
56
|
+
usageTracking: this.config.enableUsageTracking,
|
|
64
57
|
});
|
|
65
58
|
}
|
|
66
|
-
|
|
59
|
+
|
|
67
60
|
/**
|
|
68
61
|
* Main entry point - intelligently handles all component operations
|
|
69
62
|
*/
|
|
70
|
-
async loadComponent(
|
|
71
|
-
spec: ComponentSpec,
|
|
72
|
-
options: LoadOptions = {}
|
|
73
|
-
): Promise<LoadResult> {
|
|
63
|
+
async loadComponent(spec: ComponentSpec, options: LoadOptions = {}): Promise<LoadResult> {
|
|
74
64
|
const startTime = Date.now();
|
|
75
65
|
const componentKey = this.getComponentKey(spec, options);
|
|
76
|
-
|
|
77
|
-
this.log(`Loading component: ${spec.name}`, {
|
|
78
|
-
key: componentKey,
|
|
66
|
+
|
|
67
|
+
this.log(`Loading component: ${spec.name}`, {
|
|
68
|
+
key: componentKey,
|
|
79
69
|
location: spec.location,
|
|
80
70
|
registry: spec.registry,
|
|
81
|
-
forceRefresh: options.forceRefresh
|
|
71
|
+
forceRefresh: options.forceRefresh,
|
|
82
72
|
});
|
|
83
|
-
|
|
73
|
+
|
|
84
74
|
// Check if already loading to prevent duplicate work
|
|
85
75
|
const existingPromise = this.loadingPromises.get(componentKey);
|
|
86
76
|
if (existingPromise && !options.forceRefresh) {
|
|
87
77
|
this.log(`Component already loading: ${spec.name}, waiting...`);
|
|
88
78
|
return existingPromise;
|
|
89
79
|
}
|
|
90
|
-
|
|
80
|
+
|
|
91
81
|
// Create loading promise
|
|
92
82
|
const loadPromise = this.doLoadComponent(spec, options, componentKey, startTime);
|
|
93
83
|
this.loadingPromises.set(componentKey, loadPromise);
|
|
94
|
-
|
|
84
|
+
|
|
95
85
|
try {
|
|
96
86
|
const result = await loadPromise;
|
|
97
87
|
return result;
|
|
@@ -99,50 +89,45 @@ export class ComponentManager {
|
|
|
99
89
|
this.loadingPromises.delete(componentKey);
|
|
100
90
|
}
|
|
101
91
|
}
|
|
102
|
-
|
|
92
|
+
|
|
103
93
|
/**
|
|
104
94
|
* Internal method that does the actual loading
|
|
105
95
|
*/
|
|
106
|
-
private async doLoadComponent(
|
|
107
|
-
spec: ComponentSpec,
|
|
108
|
-
options: LoadOptions,
|
|
109
|
-
componentKey: string,
|
|
110
|
-
startTime: number
|
|
111
|
-
): Promise<LoadResult> {
|
|
96
|
+
private async doLoadComponent(spec: ComponentSpec, options: LoadOptions, componentKey: string, startTime: number): Promise<LoadResult> {
|
|
112
97
|
const errors: LoadResult['errors'] = [];
|
|
113
|
-
|
|
98
|
+
|
|
114
99
|
try {
|
|
115
100
|
// STEP 1: Check if already loaded in ComponentRegistry
|
|
116
101
|
const namespace = spec.namespace || options.defaultNamespace || 'Global';
|
|
117
102
|
const version = spec.version || options.defaultVersion || 'latest';
|
|
118
|
-
|
|
103
|
+
|
|
119
104
|
const existing = this.registry.get(spec.name, namespace, version);
|
|
120
105
|
if (existing && !options.forceRefresh && !options.forceRecompile) {
|
|
121
106
|
this.log(`Component found in registry: ${spec.name}`);
|
|
122
|
-
|
|
107
|
+
|
|
123
108
|
// Still need to notify registry for usage tracking
|
|
124
109
|
if (options.trackUsage !== false) {
|
|
125
110
|
await this.notifyRegistryUsageIfNeeded(spec, componentKey);
|
|
126
111
|
}
|
|
127
|
-
|
|
112
|
+
|
|
128
113
|
// Get cached spec if available
|
|
129
114
|
const cachedEntry = this.fetchCache.get(componentKey);
|
|
130
|
-
|
|
115
|
+
|
|
131
116
|
return {
|
|
132
117
|
success: true,
|
|
133
118
|
component: existing,
|
|
134
119
|
spec: cachedEntry?.spec || spec,
|
|
135
|
-
fromCache: true
|
|
120
|
+
fromCache: true,
|
|
136
121
|
};
|
|
137
122
|
}
|
|
138
|
-
|
|
123
|
+
|
|
139
124
|
// STEP 2: Fetch full spec if needed
|
|
140
125
|
let fullSpec = spec;
|
|
141
126
|
if (this.needsFetch(spec)) {
|
|
142
127
|
this.log(`Fetching component spec: ${spec.name}`);
|
|
143
128
|
try {
|
|
144
129
|
fullSpec = await this.fetchComponentSpec(spec, options.contextUser, {
|
|
145
|
-
resolutionMode: options.resolutionMode
|
|
130
|
+
resolutionMode: options.resolutionMode,
|
|
146
131
|
});
|
|
147
132
|
|
|
148
133
|
// Cache the fetched spec
|
|
@@ -150,13 +135,13 @@ export class ComponentManager {
|
|
|
150
135
|
spec: fullSpec,
|
|
151
136
|
fetchedAt: new Date(),
|
|
152
137
|
hash: await this.calculateHash(fullSpec),
|
|
153
|
-
usageNotified: false
|
|
138
|
+
usageNotified: false,
|
|
154
139
|
});
|
|
155
140
|
} catch (error) {
|
|
156
141
|
errors.push({
|
|
157
142
|
message: `Failed to fetch component: ${error instanceof Error ? error.message : String(error)}`,
|
|
158
143
|
phase: 'fetch',
|
|
159
|
-
componentName: spec.name
|
|
144
|
+
componentName: spec.name,
|
|
160
145
|
});
|
|
161
146
|
throw error;
|
|
162
147
|
}
|
|
@@ -165,7 +150,7 @@ export class ComponentManager {
|
|
|
165
150
|
if (spec.location === 'registry' && spec.code) {
|
|
166
151
|
this.log(`Skipping fetch for registry component: ${spec.name} (code already provided)`, {
|
|
167
152
|
location: spec.location,
|
|
168
|
-
registry: spec.registry
|
|
153
|
+
registry: spec.registry,
|
|
169
154
|
});
|
|
170
155
|
}
|
|
171
156
|
// Also cache the spec if it has code to avoid re-fetching
|
|
@@ -174,16 +159,16 @@ export class ComponentManager {
|
|
|
174
159
|
spec: fullSpec,
|
|
175
160
|
fetchedAt: new Date(),
|
|
176
161
|
hash: await this.calculateHash(fullSpec),
|
|
177
|
-
usageNotified: false
|
|
162
|
+
usageNotified: false,
|
|
178
163
|
});
|
|
179
164
|
}
|
|
180
165
|
}
|
|
181
|
-
|
|
166
|
+
|
|
182
167
|
// STEP 3: Notify registry of usage (exactly once per session)
|
|
183
168
|
if (options.trackUsage !== false) {
|
|
184
169
|
await this.notifyRegistryUsageIfNeeded(fullSpec, componentKey);
|
|
185
170
|
}
|
|
186
|
-
|
|
171
|
+
|
|
187
172
|
// STEP 4: Compile if needed
|
|
188
173
|
let compiledComponent = existing;
|
|
189
174
|
if (!compiledComponent || options.forceRecompile) {
|
|
@@ -194,39 +179,29 @@ export class ComponentManager {
|
|
|
194
179
|
errors.push({
|
|
195
180
|
message: `Failed to compile component: ${error instanceof Error ? error.message : String(error)}`,
|
|
196
181
|
phase: 'compile',
|
|
197
|
-
componentName: spec.name
|
|
182
|
+
componentName: spec.name,
|
|
198
183
|
});
|
|
199
184
|
throw error;
|
|
200
185
|
}
|
|
201
186
|
}
|
|
202
|
-
|
|
187
|
+
|
|
203
188
|
// STEP 5: Register in ComponentRegistry
|
|
204
189
|
if (!existing || options.forceRefresh || options.forceRecompile) {
|
|
205
190
|
this.log(`Registering component: ${spec.name}`);
|
|
206
|
-
this.registry.register(
|
|
207
|
-
fullSpec.name,
|
|
208
|
-
compiledComponent,
|
|
209
|
-
namespace,
|
|
210
|
-
version
|
|
211
|
-
);
|
|
191
|
+
this.registry.register(fullSpec.name, compiledComponent, namespace, version);
|
|
212
192
|
}
|
|
213
|
-
|
|
193
|
+
|
|
214
194
|
// STEP 6: Process dependencies recursively
|
|
215
195
|
const dependencies: Record<string, ComponentObject> = {};
|
|
216
196
|
if (fullSpec.dependencies && fullSpec.dependencies.length > 0) {
|
|
217
197
|
this.log(`Loading ${fullSpec.dependencies.length} dependencies for ${spec.name}`);
|
|
218
|
-
|
|
198
|
+
|
|
219
199
|
// Load dependencies in batches for efficiency
|
|
220
|
-
const depResults = await this.loadDependenciesBatched(
|
|
221
|
-
|
|
222
|
-
{ ...options, isDependent: true }
|
|
223
|
-
);
|
|
224
|
-
|
|
200
|
+
const depResults = await this.loadDependenciesBatched(fullSpec.dependencies, { ...options, isDependent: true });
|
|
201
|
+
|
|
225
202
|
for (const result of depResults) {
|
|
226
203
|
if (result.success && result.component) {
|
|
227
|
-
const depSpec = fullSpec.dependencies.find(d =>
|
|
228
|
-
d.name === (result.spec?.name || '')
|
|
229
|
-
);
|
|
204
|
+
const depSpec = fullSpec.dependencies.find((d) => d.name === (result.spec?.name || ''));
|
|
230
205
|
if (depSpec) {
|
|
231
206
|
dependencies[depSpec.name] = result.component;
|
|
232
207
|
}
|
|
@@ -235,45 +210,46 @@ export class ComponentManager {
|
|
|
235
210
|
}
|
|
236
211
|
}
|
|
237
212
|
}
|
|
238
|
-
|
|
213
|
+
|
|
239
214
|
const elapsed = Date.now() - startTime;
|
|
240
215
|
this.log(`Component loaded successfully: ${spec.name} (${elapsed}ms)`, {
|
|
241
216
|
fromCache: false,
|
|
242
|
-
dependencyCount: Object.keys(dependencies).length
|
|
217
|
+
dependencyCount: Object.keys(dependencies).length,
|
|
243
218
|
});
|
|
244
|
-
|
|
219
|
+
|
|
245
220
|
return {
|
|
246
221
|
success: errors.length === 0,
|
|
247
222
|
component: compiledComponent,
|
|
248
223
|
spec: fullSpec,
|
|
249
224
|
fromCache: false,
|
|
250
225
|
dependencies,
|
|
251
|
-
errors: errors.length > 0 ? errors : undefined
|
|
226
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
252
227
|
};
|
|
253
|
-
|
|
254
228
|
} catch (error) {
|
|
255
229
|
const elapsed = Date.now() - startTime;
|
|
256
230
|
this.log(`Failed to load component: ${spec.name} (${elapsed}ms)`, error);
|
|
257
|
-
|
|
231
|
+
|
|
258
232
|
return {
|
|
259
233
|
success: false,
|
|
260
234
|
fromCache: false,
|
|
261
|
-
errors:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
235
|
+
errors:
|
|
236
|
+
errors.length > 0
|
|
237
|
+
? errors
|
|
238
|
+
: [
|
|
239
|
+
{
|
|
240
|
+
message: error instanceof Error ? error.message : String(error),
|
|
241
|
+
phase: 'fetch',
|
|
242
|
+
componentName: spec.name,
|
|
243
|
+
},
|
|
244
|
+
],
|
|
266
245
|
};
|
|
267
246
|
}
|
|
268
247
|
}
|
|
269
|
-
|
|
248
|
+
|
|
270
249
|
/**
|
|
271
250
|
* Load a complete hierarchy efficiently
|
|
272
251
|
*/
|
|
273
|
-
async loadHierarchy(
|
|
274
|
-
rootSpec: ComponentSpec,
|
|
275
|
-
options: LoadOptions = {}
|
|
276
|
-
): Promise<HierarchyResult> {
|
|
252
|
+
async loadHierarchy(rootSpec: ComponentSpec, options: LoadOptions = {}): Promise<HierarchyResult> {
|
|
277
253
|
const startTime = Date.now();
|
|
278
254
|
const loaded: string[] = [];
|
|
279
255
|
const errors: HierarchyResult['errors'] = [];
|
|
@@ -282,40 +258,32 @@ export class ComponentManager {
|
|
|
282
258
|
fromCache: 0,
|
|
283
259
|
fetched: 0,
|
|
284
260
|
compiled: 0,
|
|
285
|
-
totalTime: 0
|
|
261
|
+
totalTime: 0,
|
|
286
262
|
};
|
|
287
|
-
|
|
263
|
+
|
|
288
264
|
this.log(`Loading component hierarchy: ${rootSpec.name}`, {
|
|
289
265
|
location: rootSpec.location,
|
|
290
|
-
registry: rootSpec.registry
|
|
266
|
+
registry: rootSpec.registry,
|
|
291
267
|
});
|
|
292
|
-
|
|
268
|
+
|
|
293
269
|
try {
|
|
294
270
|
// Initialize component engine if needed (skip in browser context where it doesn't exist)
|
|
295
271
|
if (this.componentEngine && typeof this.componentEngine.Config === 'function') {
|
|
296
272
|
await this.componentEngine.Config(false, options.contextUser);
|
|
297
273
|
}
|
|
298
|
-
|
|
274
|
+
|
|
299
275
|
// Load the root component and all its dependencies
|
|
300
|
-
const result = await this.loadComponentRecursive(
|
|
301
|
-
|
|
302
|
-
options,
|
|
303
|
-
loaded,
|
|
304
|
-
errors,
|
|
305
|
-
components,
|
|
306
|
-
stats,
|
|
307
|
-
new Set()
|
|
308
|
-
);
|
|
309
|
-
|
|
276
|
+
const result = await this.loadComponentRecursive(rootSpec, options, loaded, errors, components, stats, new Set());
|
|
277
|
+
|
|
310
278
|
stats.totalTime = Date.now() - startTime;
|
|
311
|
-
|
|
279
|
+
|
|
312
280
|
this.log(`Hierarchy loaded: ${rootSpec.name}`, {
|
|
313
281
|
success: errors.length === 0,
|
|
314
282
|
loadedCount: loaded.length,
|
|
315
283
|
errors: errors.length,
|
|
316
|
-
...stats
|
|
284
|
+
...stats,
|
|
317
285
|
});
|
|
318
|
-
|
|
286
|
+
|
|
319
287
|
// Unwrap components before returning
|
|
320
288
|
// Components are ComponentObject wrappers, but consumers expect just the React components
|
|
321
289
|
const unwrappedComponents: Record<string, ComponentObject> = {};
|
|
@@ -328,7 +296,7 @@ export class ComponentManager {
|
|
|
328
296
|
unwrappedComponents[name] = componentObject;
|
|
329
297
|
}
|
|
330
298
|
}
|
|
331
|
-
|
|
299
|
+
|
|
332
300
|
return {
|
|
333
301
|
success: errors.length === 0,
|
|
334
302
|
rootComponent: result.component,
|
|
@@ -336,27 +304,29 @@ export class ComponentManager {
|
|
|
336
304
|
loadedComponents: loaded,
|
|
337
305
|
errors,
|
|
338
306
|
components: unwrappedComponents,
|
|
339
|
-
stats
|
|
307
|
+
stats,
|
|
340
308
|
};
|
|
341
|
-
|
|
342
309
|
} catch (error) {
|
|
343
310
|
stats.totalTime = Date.now() - startTime;
|
|
344
|
-
|
|
311
|
+
|
|
345
312
|
this.log(`Failed to load hierarchy: ${rootSpec.name}`, error);
|
|
346
|
-
|
|
313
|
+
|
|
347
314
|
return {
|
|
348
315
|
success: false,
|
|
349
316
|
loadedComponents: loaded,
|
|
350
|
-
errors: [
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
317
|
+
errors: [
|
|
318
|
+
...errors,
|
|
319
|
+
{
|
|
320
|
+
message: error instanceof Error ? error.message : String(error),
|
|
321
|
+
phase: 'fetch',
|
|
322
|
+
componentName: rootSpec.name,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
stats,
|
|
356
326
|
};
|
|
357
327
|
}
|
|
358
328
|
}
|
|
359
|
-
|
|
329
|
+
|
|
360
330
|
/**
|
|
361
331
|
* Recursively load a component and its dependencies
|
|
362
332
|
*/
|
|
@@ -370,7 +340,7 @@ export class ComponentManager {
|
|
|
370
340
|
visited: Set<string>
|
|
371
341
|
): Promise<LoadResult> {
|
|
372
342
|
const componentKey = this.getComponentKey(spec, options);
|
|
373
|
-
|
|
343
|
+
|
|
374
344
|
// Prevent circular dependencies
|
|
375
345
|
if (visited.has(componentKey)) {
|
|
376
346
|
this.log(`Circular dependency detected: ${spec.name}`);
|
|
@@ -378,18 +348,18 @@ export class ComponentManager {
|
|
|
378
348
|
success: true,
|
|
379
349
|
component: components[spec.name],
|
|
380
350
|
spec,
|
|
381
|
-
fromCache: true
|
|
351
|
+
fromCache: true,
|
|
382
352
|
};
|
|
383
353
|
}
|
|
384
354
|
visited.add(componentKey);
|
|
385
|
-
|
|
355
|
+
|
|
386
356
|
// Load this component
|
|
387
357
|
const result = await this.loadComponent(spec, options);
|
|
388
|
-
|
|
358
|
+
|
|
389
359
|
if (result.success && result.component) {
|
|
390
360
|
loaded.push(spec.name);
|
|
391
361
|
components[spec.name] = result.component;
|
|
392
|
-
|
|
362
|
+
|
|
393
363
|
// Update stats
|
|
394
364
|
if (stats) {
|
|
395
365
|
if (result.fromCache) stats.fromCache++;
|
|
@@ -398,7 +368,7 @@ export class ComponentManager {
|
|
|
398
368
|
stats.compiled++;
|
|
399
369
|
}
|
|
400
370
|
}
|
|
401
|
-
|
|
371
|
+
|
|
402
372
|
// Load dependencies
|
|
403
373
|
if (result.spec?.dependencies) {
|
|
404
374
|
for (const dep of result.spec.dependencies) {
|
|
@@ -421,44 +391,33 @@ export class ComponentManager {
|
|
|
421
391
|
this.log(`Dependency ${depSpec.name} is a local registry component (registry=undefined)`);
|
|
422
392
|
}
|
|
423
393
|
|
|
424
|
-
await this.loadComponentRecursive(
|
|
425
|
-
depSpec,
|
|
426
|
-
{ ...options, isDependent: true },
|
|
427
|
-
loaded,
|
|
428
|
-
errors,
|
|
429
|
-
components,
|
|
430
|
-
stats,
|
|
431
|
-
visited
|
|
432
|
-
);
|
|
394
|
+
await this.loadComponentRecursive(depSpec, { ...options, isDependent: true }, loaded, errors, components, stats, visited);
|
|
433
395
|
}
|
|
434
396
|
}
|
|
435
397
|
} else if (result.errors) {
|
|
436
398
|
errors.push(...result.errors);
|
|
437
399
|
}
|
|
438
|
-
|
|
400
|
+
|
|
439
401
|
return result;
|
|
440
402
|
}
|
|
441
|
-
|
|
403
|
+
|
|
442
404
|
/**
|
|
443
405
|
* Load dependencies in batches for efficiency
|
|
444
406
|
*/
|
|
445
|
-
private async loadDependenciesBatched(
|
|
446
|
-
dependencies: ComponentSpec[],
|
|
447
|
-
options: LoadOptions
|
|
448
|
-
): Promise<LoadResult[]> {
|
|
407
|
+
private async loadDependenciesBatched(dependencies: ComponentSpec[], options: LoadOptions): Promise<LoadResult[]> {
|
|
449
408
|
const batchSize = this.config.dependencyBatchSize || 5;
|
|
450
409
|
const results: LoadResult[] = [];
|
|
451
|
-
|
|
410
|
+
|
|
452
411
|
for (let i = 0; i < dependencies.length; i += batchSize) {
|
|
453
412
|
const batch = dependencies.slice(i, i + batchSize);
|
|
454
|
-
const batchPromises = batch.map(dep => this.loadComponent(dep, options));
|
|
413
|
+
const batchPromises = batch.map((dep) => this.loadComponent(dep, options));
|
|
455
414
|
const batchResults = await Promise.all(batchPromises);
|
|
456
415
|
results.push(...batchResults);
|
|
457
416
|
}
|
|
458
|
-
|
|
417
|
+
|
|
459
418
|
return results;
|
|
460
419
|
}
|
|
461
|
-
|
|
420
|
+
|
|
462
421
|
/**
|
|
463
422
|
* Check if a component needs to be fetched from a registry
|
|
464
423
|
*/
|
|
@@ -468,10 +427,10 @@ export class ComponentManager {
|
|
|
468
427
|
// 2. It's missing required fields
|
|
469
428
|
return spec.location === 'registry' && !spec.code;
|
|
470
429
|
}
|
|
471
|
-
|
|
430
|
+
|
|
472
431
|
/**
|
|
473
432
|
* Fetch a component specification from a registry (local or external)
|
|
474
|
-
*
|
|
433
|
+
*
|
|
475
434
|
* Convention: When location === 'registry' and registry === undefined,
|
|
476
435
|
* the component is looked up in the local ComponentMetadataEngine.
|
|
477
436
|
* This allows components to reference local registry components without
|
|
@@ -485,100 +444,94 @@ export class ComponentManager {
|
|
|
485
444
|
// Check cache first
|
|
486
445
|
const cacheKey = this.getComponentKey(spec, {});
|
|
487
446
|
const cached = this.fetchCache.get(cacheKey);
|
|
488
|
-
|
|
447
|
+
|
|
489
448
|
if (cached && this.isCacheValid(cached)) {
|
|
490
449
|
this.log(`Using cached spec for: ${spec.name}`);
|
|
491
450
|
return cached.spec;
|
|
492
451
|
}
|
|
493
|
-
|
|
452
|
+
|
|
494
453
|
// Handle LOCAL registry components (registry is null/undefined)
|
|
495
454
|
if (!spec.registry) {
|
|
496
455
|
this.log(`Fetching from local registry: ${spec.name}`);
|
|
497
|
-
|
|
456
|
+
|
|
498
457
|
// Find component in local ComponentMetadataEngine
|
|
499
|
-
const localComponent = this.componentEngine.Components?.find(
|
|
500
|
-
(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
if (nameMatch && !namespaceMatch) {
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
return nameMatch && namespaceMatch;
|
|
458
|
+
const localComponent = this.componentEngine.Components?.find((c: ComponentEntityExtended) => {
|
|
459
|
+
// Match by name (case-insensitive for better compatibility)
|
|
460
|
+
const nameMatch = c.Name?.toLowerCase() === spec.name?.toLowerCase();
|
|
461
|
+
|
|
462
|
+
// Match by namespace if provided (handle different formats)
|
|
463
|
+
const namespaceMatch = !spec.namespace || c.Namespace?.toLowerCase() === spec.namespace?.toLowerCase();
|
|
464
|
+
|
|
465
|
+
if (nameMatch && !namespaceMatch) {
|
|
511
466
|
}
|
|
512
|
-
|
|
513
|
-
|
|
467
|
+
|
|
468
|
+
return nameMatch && namespaceMatch;
|
|
469
|
+
});
|
|
470
|
+
|
|
514
471
|
if (!localComponent) {
|
|
515
472
|
throw new Error(`Local component not found: ${spec.name}`);
|
|
516
473
|
}
|
|
517
|
-
|
|
474
|
+
|
|
518
475
|
// Parse specification from local component
|
|
519
476
|
if (!localComponent.Specification) {
|
|
520
477
|
throw new Error(`Local component ${spec.name} has no specification`);
|
|
521
478
|
}
|
|
522
|
-
|
|
479
|
+
|
|
523
480
|
const fullSpec = JSON.parse(localComponent.Specification);
|
|
524
|
-
|
|
481
|
+
|
|
525
482
|
// Cache it
|
|
526
483
|
this.fetchCache.set(cacheKey, {
|
|
527
484
|
spec: fullSpec,
|
|
528
485
|
fetchedAt: new Date(),
|
|
529
|
-
usageNotified: false
|
|
486
|
+
usageNotified: false,
|
|
530
487
|
});
|
|
531
|
-
|
|
488
|
+
|
|
532
489
|
return fullSpec;
|
|
533
490
|
}
|
|
534
|
-
|
|
491
|
+
|
|
535
492
|
// Handle EXTERNAL registry components (registry has a name)
|
|
536
493
|
// Initialize GraphQL client if needed
|
|
537
494
|
if (!this.graphQLClient) {
|
|
538
495
|
await this.initializeGraphQLClient();
|
|
539
496
|
}
|
|
540
|
-
|
|
497
|
+
|
|
541
498
|
if (!this.graphQLClient) {
|
|
542
499
|
throw new Error('GraphQL client not available for registry fetching');
|
|
543
500
|
}
|
|
544
|
-
|
|
501
|
+
|
|
545
502
|
// Fetch from external registry
|
|
546
503
|
this.log(`Fetching from external registry: ${spec.registry}/${spec.name}`);
|
|
547
|
-
|
|
504
|
+
|
|
548
505
|
const fullSpec = await this.graphQLClient.GetRegistryComponent({
|
|
549
506
|
registryName: spec.registry,
|
|
550
507
|
namespace: spec.namespace || 'Global',
|
|
551
508
|
name: spec.name,
|
|
552
|
-
version: spec.version || 'latest'
|
|
509
|
+
version: spec.version || 'latest',
|
|
553
510
|
});
|
|
554
|
-
|
|
511
|
+
|
|
555
512
|
if (!fullSpec) {
|
|
556
513
|
throw new Error(`Component not found in registry: ${spec.registry}/${spec.name}`);
|
|
557
514
|
}
|
|
558
|
-
|
|
515
|
+
|
|
559
516
|
// Apply resolution mode if specified
|
|
560
517
|
const processedSpec = this.applyResolutionMode(fullSpec, spec, options?.resolutionMode);
|
|
561
|
-
|
|
518
|
+
|
|
562
519
|
// Cache it
|
|
563
520
|
this.fetchCache.set(cacheKey, {
|
|
564
521
|
spec: processedSpec,
|
|
565
522
|
fetchedAt: new Date(),
|
|
566
|
-
usageNotified: false
|
|
523
|
+
usageNotified: false,
|
|
567
524
|
});
|
|
568
|
-
|
|
525
|
+
|
|
569
526
|
return processedSpec;
|
|
570
527
|
}
|
|
571
|
-
|
|
528
|
+
|
|
572
529
|
/**
|
|
573
530
|
* Apply resolution mode to a fetched spec (recursively including dependencies)
|
|
574
531
|
*/
|
|
575
|
-
private applyResolutionMode(
|
|
576
|
-
fullSpec: ComponentSpec,
|
|
577
|
-
originalSpec: ComponentSpec,
|
|
578
|
-
resolutionMode?: ResolutionMode
|
|
579
|
-
): ComponentSpec {
|
|
532
|
+
private applyResolutionMode(fullSpec: ComponentSpec, originalSpec: ComponentSpec, resolutionMode?: ResolutionMode): ComponentSpec {
|
|
580
533
|
let processedSpec: ComponentSpec;
|
|
581
|
-
|
|
534
|
+
|
|
582
535
|
if (resolutionMode === 'embed') {
|
|
583
536
|
// Convert to embedded format for test harness
|
|
584
537
|
processedSpec = {
|
|
@@ -595,51 +548,47 @@ export class ComponentManager {
|
|
|
595
548
|
location: originalSpec.location,
|
|
596
549
|
registry: originalSpec.registry,
|
|
597
550
|
namespace: originalSpec.namespace || fullSpec.namespace,
|
|
598
|
-
name: originalSpec.name || fullSpec.name
|
|
551
|
+
name: originalSpec.name || fullSpec.name,
|
|
599
552
|
};
|
|
600
553
|
}
|
|
601
|
-
|
|
554
|
+
|
|
602
555
|
// Recursively apply resolution mode to dependencies
|
|
603
556
|
if (processedSpec.dependencies && processedSpec.dependencies.length > 0) {
|
|
604
|
-
processedSpec.dependencies = processedSpec.dependencies.map(dep => {
|
|
557
|
+
processedSpec.dependencies = processedSpec.dependencies.map((dep) => {
|
|
605
558
|
// For dependencies, use the dep itself as both full and original spec
|
|
606
559
|
// since they've already been fetched and processed
|
|
607
560
|
return this.applyResolutionMode(dep, dep, resolutionMode);
|
|
608
561
|
});
|
|
609
562
|
}
|
|
610
|
-
|
|
563
|
+
|
|
611
564
|
return processedSpec;
|
|
612
565
|
}
|
|
613
566
|
|
|
614
567
|
/**
|
|
615
568
|
* Compile a component specification
|
|
616
569
|
*/
|
|
617
|
-
private async compileComponent(
|
|
618
|
-
spec: ComponentSpec,
|
|
619
|
-
options: LoadOptions
|
|
620
|
-
): Promise<ComponentObject> {
|
|
570
|
+
private async compileComponent(spec: ComponentSpec, options: LoadOptions): Promise<ComponentObject> {
|
|
621
571
|
// Get all available libraries - use passed libraries or fall back to ComponentMetadataEngine
|
|
622
572
|
const allLibraries = options.allLibraries || this.componentEngine.ComponentLibraries || [];
|
|
623
|
-
|
|
573
|
+
|
|
624
574
|
// Filter valid libraries
|
|
625
|
-
const validLibraries = spec.libraries?.filter(
|
|
626
|
-
lib && lib.name && lib.globalVariable &&
|
|
627
|
-
lib.name !== 'unknown' && lib.globalVariable !== 'undefined'
|
|
575
|
+
const validLibraries = spec.libraries?.filter(
|
|
576
|
+
(lib) => lib && lib.name && lib.globalVariable && lib.name !== 'unknown' && lib.globalVariable !== 'undefined'
|
|
628
577
|
);
|
|
629
|
-
|
|
578
|
+
|
|
630
579
|
// Compile the component
|
|
631
580
|
const result = await this.compiler.compile({
|
|
632
581
|
componentName: spec.name,
|
|
633
582
|
componentCode: spec.code || '',
|
|
634
583
|
libraries: validLibraries,
|
|
635
584
|
dependencies: spec.dependencies,
|
|
636
|
-
allLibraries
|
|
585
|
+
allLibraries,
|
|
637
586
|
});
|
|
638
|
-
|
|
587
|
+
|
|
639
588
|
if (!result.success || !result.component) {
|
|
640
589
|
throw new Error(result.error?.message || 'Compilation failed');
|
|
641
590
|
}
|
|
642
|
-
|
|
591
|
+
|
|
643
592
|
// Add loaded libraries to runtime context
|
|
644
593
|
if (result.loadedLibraries && result.loadedLibraries.size > 0) {
|
|
645
594
|
if (!this.runtimeContext.libraries) {
|
|
@@ -649,41 +598,38 @@ export class ComponentManager {
|
|
|
649
598
|
this.runtimeContext.libraries![key] = value;
|
|
650
599
|
});
|
|
651
600
|
}
|
|
652
|
-
|
|
601
|
+
|
|
653
602
|
// Get the component object from the factory
|
|
654
603
|
const componentObject = result.component.factory(
|
|
655
604
|
this.runtimeContext,
|
|
656
605
|
undefined, // styles
|
|
657
606
|
{} // components - will be injected by parent
|
|
658
607
|
);
|
|
659
|
-
|
|
608
|
+
|
|
660
609
|
return componentObject;
|
|
661
610
|
}
|
|
662
|
-
|
|
611
|
+
|
|
663
612
|
/**
|
|
664
613
|
* Notify registry of component usage for licensing
|
|
665
614
|
* Only happens once per component per session
|
|
666
615
|
*/
|
|
667
|
-
private async notifyRegistryUsageIfNeeded(
|
|
668
|
-
spec: ComponentSpec,
|
|
669
|
-
componentKey: string
|
|
670
|
-
): Promise<void> {
|
|
616
|
+
private async notifyRegistryUsageIfNeeded(spec: ComponentSpec, componentKey: string): Promise<void> {
|
|
671
617
|
if (!spec.registry || !this.config.enableUsageTracking) {
|
|
672
618
|
return; // Only for external registry components with tracking enabled
|
|
673
619
|
}
|
|
674
|
-
|
|
620
|
+
|
|
675
621
|
const notificationKey = `${spec.registry}:${componentKey}`;
|
|
676
622
|
if (this.registryNotifications.has(notificationKey)) {
|
|
677
623
|
this.log(`Usage already notified for: ${spec.name}`);
|
|
678
624
|
return; // Already notified this session
|
|
679
625
|
}
|
|
680
|
-
|
|
626
|
+
|
|
681
627
|
try {
|
|
682
628
|
// In the future, make lightweight usage notification call to registry
|
|
683
629
|
// For now, the fetch itself serves as the notification
|
|
684
630
|
this.log(`Notifying registry usage for: ${spec.name}`);
|
|
685
631
|
this.registryNotifications.add(notificationKey);
|
|
686
|
-
|
|
632
|
+
|
|
687
633
|
// Update cache entry
|
|
688
634
|
const cached = this.fetchCache.get(componentKey);
|
|
689
635
|
if (cached) {
|
|
@@ -694,7 +640,7 @@ export class ComponentManager {
|
|
|
694
640
|
console.warn(`Failed to notify registry usage for ${componentKey}:`, error);
|
|
695
641
|
}
|
|
696
642
|
}
|
|
697
|
-
|
|
643
|
+
|
|
698
644
|
/**
|
|
699
645
|
* Initialize GraphQL client for registry operations
|
|
700
646
|
*/
|
|
@@ -710,7 +656,7 @@ export class ComponentManager {
|
|
|
710
656
|
LogError(`Failed to initialize GraphQL client: ${error instanceof Error ? error.message : String(error)}`);
|
|
711
657
|
}
|
|
712
658
|
}
|
|
713
|
-
|
|
659
|
+
|
|
714
660
|
/**
|
|
715
661
|
* Check if a cache entry is still valid
|
|
716
662
|
*/
|
|
@@ -718,7 +664,7 @@ export class ComponentManager {
|
|
|
718
664
|
const age = Date.now() - entry.fetchedAt.getTime();
|
|
719
665
|
return age < (this.config.cacheTTL || 3600000);
|
|
720
666
|
}
|
|
721
|
-
|
|
667
|
+
|
|
722
668
|
/**
|
|
723
669
|
* Calculate a hash for a component spec (for cache validation)
|
|
724
670
|
*/
|
|
@@ -728,19 +674,19 @@ export class ComponentManager {
|
|
|
728
674
|
name: spec.name,
|
|
729
675
|
version: spec.version,
|
|
730
676
|
code: spec.code,
|
|
731
|
-
libraries: spec.libraries
|
|
677
|
+
libraries: spec.libraries,
|
|
732
678
|
});
|
|
733
|
-
|
|
679
|
+
|
|
734
680
|
// Simple hash function (in production, use crypto)
|
|
735
681
|
let hash = 0;
|
|
736
682
|
for (let i = 0; i < content.length; i++) {
|
|
737
683
|
const char = content.charCodeAt(i);
|
|
738
|
-
hash = (
|
|
684
|
+
hash = (hash << 5) - hash + char;
|
|
739
685
|
hash = hash & hash; // Convert to 32bit integer
|
|
740
686
|
}
|
|
741
687
|
return hash.toString(16);
|
|
742
688
|
}
|
|
743
|
-
|
|
689
|
+
|
|
744
690
|
/**
|
|
745
691
|
* Generate a unique key for a component
|
|
746
692
|
*/
|
|
@@ -750,7 +696,7 @@ export class ComponentManager {
|
|
|
750
696
|
const version = spec.version || options.defaultVersion || 'latest';
|
|
751
697
|
return `${registry}:${namespace}:${spec.name}:${version}`;
|
|
752
698
|
}
|
|
753
|
-
|
|
699
|
+
|
|
754
700
|
/**
|
|
755
701
|
* Clear all caches
|
|
756
702
|
*/
|
|
@@ -760,7 +706,7 @@ export class ComponentManager {
|
|
|
760
706
|
this.loadingPromises.clear();
|
|
761
707
|
this.log('All caches cleared');
|
|
762
708
|
}
|
|
763
|
-
|
|
709
|
+
|
|
764
710
|
/**
|
|
765
711
|
* Get cache statistics
|
|
766
712
|
*/
|
|
@@ -772,10 +718,10 @@ export class ComponentManager {
|
|
|
772
718
|
return {
|
|
773
719
|
fetchCacheSize: this.fetchCache.size,
|
|
774
720
|
notificationsCount: this.registryNotifications.size,
|
|
775
|
-
loadingCount: this.loadingPromises.size
|
|
721
|
+
loadingCount: this.loadingPromises.size,
|
|
776
722
|
};
|
|
777
723
|
}
|
|
778
|
-
|
|
724
|
+
|
|
779
725
|
/**
|
|
780
726
|
* Log a message if debug is enabled
|
|
781
727
|
*/
|
|
@@ -784,4 +730,4 @@ export class ComponentManager {
|
|
|
784
730
|
console.log(`🎯 [ComponentManager] ${message}`, data || '');
|
|
785
731
|
}
|
|
786
732
|
}
|
|
787
|
-
}
|
|
733
|
+
}
|