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