@memberjunction/react-runtime 2.70.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 +4 -0
- package/CHANGELOG.md +3 -0
- package/README.md +224 -0
- package/dist/compiler/babel-config.d.ts +40 -0
- package/dist/compiler/babel-config.d.ts.map +1 -0
- package/dist/compiler/babel-config.js +52 -0
- package/dist/compiler/component-compiler.d.ts +22 -0
- package/dist/compiler/component-compiler.d.ts.map +1 -0
- package/dist/compiler/component-compiler.js +188 -0
- package/dist/compiler/index.d.ts +3 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +13 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/registry/component-registry.d.ts +32 -0
- package/dist/registry/component-registry.d.ts.map +1 -0
- package/dist/registry/component-registry.js +197 -0
- package/dist/registry/component-resolver.d.ts +29 -0
- package/dist/registry/component-resolver.d.ts.map +1 -0
- package/dist/registry/component-resolver.js +112 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +7 -0
- package/dist/runtime/component-hierarchy.d.ts +44 -0
- package/dist/runtime/component-hierarchy.d.ts.map +1 -0
- package/dist/runtime/component-hierarchy.js +162 -0
- package/dist/runtime/component-wrapper.d.ts +18 -0
- package/dist/runtime/component-wrapper.d.ts.map +1 -0
- package/dist/runtime/component-wrapper.js +108 -0
- package/dist/runtime/error-boundary.d.ts +6 -0
- package/dist/runtime/error-boundary.d.ts.map +1 -0
- package/dist/runtime/error-boundary.js +139 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +31 -0
- package/dist/runtime/prop-builder.d.ts +16 -0
- package/dist/runtime/prop-builder.d.ts.map +1 -0
- package/dist/runtime/prop-builder.js +161 -0
- package/dist/types/index.d.ts +98 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/package.json +36 -0
- package/src/compiler/babel-config.ts +97 -0
- package/src/compiler/component-compiler.ts +366 -0
- package/src/compiler/index.ts +15 -0
- package/src/index.ts +125 -0
- package/src/registry/component-registry.ts +379 -0
- package/src/registry/component-resolver.ts +275 -0
- package/src/registry/index.ts +7 -0
- package/src/runtime/component-hierarchy.ts +346 -0
- package/src/runtime/component-wrapper.ts +249 -0
- package/src/runtime/error-boundary.ts +242 -0
- package/src/runtime/index.ts +45 -0
- package/src/runtime/prop-builder.ts +290 -0
- package/src/types/index.ts +226 -0
- package/tsconfig.json +37 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Platform-agnostic component registry for managing compiled React components.
|
|
3
|
+
* Provides storage, retrieval, and lifecycle management for components with namespace support.
|
|
4
|
+
* @module @memberjunction/react-runtime/registry
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
RegistryEntry,
|
|
9
|
+
ComponentMetadata,
|
|
10
|
+
RegistryConfig
|
|
11
|
+
} from '../types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default registry configuration
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_REGISTRY_CONFIG: RegistryConfig = {
|
|
17
|
+
maxComponents: 1000,
|
|
18
|
+
cleanupInterval: 60000, // 1 minute
|
|
19
|
+
useLRU: true,
|
|
20
|
+
enableNamespaces: true
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Platform-agnostic component registry.
|
|
25
|
+
* Manages compiled React components with namespace isolation and lifecycle management.
|
|
26
|
+
*/
|
|
27
|
+
export class ComponentRegistry {
|
|
28
|
+
private registry: Map<string, RegistryEntry>;
|
|
29
|
+
private config: RegistryConfig;
|
|
30
|
+
private cleanupTimer?: NodeJS.Timeout | number;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a new ComponentRegistry instance
|
|
34
|
+
* @param config - Optional registry configuration
|
|
35
|
+
*/
|
|
36
|
+
constructor(config?: Partial<RegistryConfig>) {
|
|
37
|
+
this.config = { ...DEFAULT_REGISTRY_CONFIG, ...config };
|
|
38
|
+
this.registry = new Map();
|
|
39
|
+
|
|
40
|
+
// Start cleanup timer if configured
|
|
41
|
+
if (this.config.cleanupInterval > 0) {
|
|
42
|
+
this.startCleanupTimer();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Registers a compiled component
|
|
48
|
+
* @param name - Component name
|
|
49
|
+
* @param component - Compiled component
|
|
50
|
+
* @param namespace - Component namespace (default: 'Global')
|
|
51
|
+
* @param version - Component version (default: 'v1')
|
|
52
|
+
* @param tags - Optional tags for categorization
|
|
53
|
+
* @returns The registered component's metadata
|
|
54
|
+
*/
|
|
55
|
+
register(
|
|
56
|
+
name: string,
|
|
57
|
+
component: any,
|
|
58
|
+
namespace: string = 'Global',
|
|
59
|
+
version: string = 'v1',
|
|
60
|
+
tags?: string[]
|
|
61
|
+
): ComponentMetadata {
|
|
62
|
+
const id = this.generateRegistryKey(name, namespace, version);
|
|
63
|
+
|
|
64
|
+
// Create metadata
|
|
65
|
+
const metadata: ComponentMetadata = {
|
|
66
|
+
id,
|
|
67
|
+
name,
|
|
68
|
+
version,
|
|
69
|
+
namespace,
|
|
70
|
+
registeredAt: new Date(),
|
|
71
|
+
tags
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Create registry entry
|
|
75
|
+
const entry: RegistryEntry = {
|
|
76
|
+
component,
|
|
77
|
+
metadata,
|
|
78
|
+
lastAccessed: new Date(),
|
|
79
|
+
refCount: 0
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Check capacity
|
|
83
|
+
if (this.registry.size >= this.config.maxComponents && this.config.useLRU) {
|
|
84
|
+
this.evictLRU();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Store in registry
|
|
88
|
+
this.registry.set(id, entry);
|
|
89
|
+
|
|
90
|
+
return metadata;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Gets a component from the registry
|
|
95
|
+
* @param name - Component name
|
|
96
|
+
* @param namespace - Component namespace
|
|
97
|
+
* @param version - Component version
|
|
98
|
+
* @returns The component if found, undefined otherwise
|
|
99
|
+
*/
|
|
100
|
+
get(name: string, namespace: string = 'Global', version?: string): any {
|
|
101
|
+
const id = version
|
|
102
|
+
? this.generateRegistryKey(name, namespace, version)
|
|
103
|
+
: this.findLatestVersion(name, namespace);
|
|
104
|
+
|
|
105
|
+
if (!id) return undefined;
|
|
106
|
+
|
|
107
|
+
const entry = this.registry.get(id);
|
|
108
|
+
if (entry) {
|
|
109
|
+
// Update access time and increment ref count
|
|
110
|
+
entry.lastAccessed = new Date();
|
|
111
|
+
entry.refCount++;
|
|
112
|
+
return entry.component;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Checks if a component exists in the registry
|
|
120
|
+
* @param name - Component name
|
|
121
|
+
* @param namespace - Component namespace
|
|
122
|
+
* @param version - Component version
|
|
123
|
+
* @returns true if the component exists
|
|
124
|
+
*/
|
|
125
|
+
has(name: string, namespace: string = 'Global', version?: string): boolean {
|
|
126
|
+
const id = version
|
|
127
|
+
? this.generateRegistryKey(name, namespace, version)
|
|
128
|
+
: this.findLatestVersion(name, namespace);
|
|
129
|
+
|
|
130
|
+
return id ? this.registry.has(id) : false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Removes a component from the registry
|
|
135
|
+
* @param name - Component name
|
|
136
|
+
* @param namespace - Component namespace
|
|
137
|
+
* @param version - Component version
|
|
138
|
+
* @returns true if the component was removed
|
|
139
|
+
*/
|
|
140
|
+
unregister(name: string, namespace: string = 'Global', version?: string): boolean {
|
|
141
|
+
const id = version
|
|
142
|
+
? this.generateRegistryKey(name, namespace, version)
|
|
143
|
+
: this.findLatestVersion(name, namespace);
|
|
144
|
+
|
|
145
|
+
if (!id) return false;
|
|
146
|
+
|
|
147
|
+
return this.registry.delete(id);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Gets all components in a namespace
|
|
152
|
+
* @param namespace - Namespace to query
|
|
153
|
+
* @returns Array of components in the namespace
|
|
154
|
+
*/
|
|
155
|
+
getNamespace(namespace: string): ComponentMetadata[] {
|
|
156
|
+
const components: ComponentMetadata[] = [];
|
|
157
|
+
|
|
158
|
+
for (const entry of this.registry.values()) {
|
|
159
|
+
if (entry.metadata.namespace === namespace) {
|
|
160
|
+
components.push(entry.metadata);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return components;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Gets all registered namespaces
|
|
169
|
+
* @returns Array of unique namespace names
|
|
170
|
+
*/
|
|
171
|
+
getNamespaces(): string[] {
|
|
172
|
+
const namespaces = new Set<string>();
|
|
173
|
+
|
|
174
|
+
for (const entry of this.registry.values()) {
|
|
175
|
+
namespaces.add(entry.metadata.namespace);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return Array.from(namespaces);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Gets components by tags
|
|
183
|
+
* @param tags - Tags to search for
|
|
184
|
+
* @returns Array of components matching any of the tags
|
|
185
|
+
*/
|
|
186
|
+
getByTags(tags: string[]): ComponentMetadata[] {
|
|
187
|
+
const components: ComponentMetadata[] = [];
|
|
188
|
+
|
|
189
|
+
for (const entry of this.registry.values()) {
|
|
190
|
+
if (entry.metadata.tags?.some(tag => tags.includes(tag))) {
|
|
191
|
+
components.push(entry.metadata);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return components;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Decrements reference count for a component
|
|
200
|
+
* @param name - Component name
|
|
201
|
+
* @param namespace - Component namespace
|
|
202
|
+
* @param version - Component version
|
|
203
|
+
*/
|
|
204
|
+
release(name: string, namespace: string = 'Global', version?: string): void {
|
|
205
|
+
const id = version
|
|
206
|
+
? this.generateRegistryKey(name, namespace, version)
|
|
207
|
+
: this.findLatestVersion(name, namespace);
|
|
208
|
+
|
|
209
|
+
if (!id) return;
|
|
210
|
+
|
|
211
|
+
const entry = this.registry.get(id);
|
|
212
|
+
if (entry && entry.refCount > 0) {
|
|
213
|
+
entry.refCount--;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Clears all components from the registry
|
|
219
|
+
*/
|
|
220
|
+
clear(): void {
|
|
221
|
+
this.registry.clear();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Gets the current size of the registry
|
|
226
|
+
* @returns Number of registered components
|
|
227
|
+
*/
|
|
228
|
+
size(): number {
|
|
229
|
+
return this.registry.size;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Performs cleanup of unused components
|
|
234
|
+
* @param force - Force cleanup regardless of reference count
|
|
235
|
+
* @returns Number of components removed
|
|
236
|
+
*/
|
|
237
|
+
cleanup(force: boolean = false): number {
|
|
238
|
+
const toRemove: string[] = [];
|
|
239
|
+
const now = Date.now();
|
|
240
|
+
|
|
241
|
+
for (const [id, entry] of this.registry) {
|
|
242
|
+
// Remove if no references and hasn't been accessed recently
|
|
243
|
+
const timeSinceAccess = now - entry.lastAccessed.getTime();
|
|
244
|
+
const isUnused = entry.refCount === 0 && timeSinceAccess > this.config.cleanupInterval;
|
|
245
|
+
|
|
246
|
+
if (force || isUnused) {
|
|
247
|
+
toRemove.push(id);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const id of toRemove) {
|
|
252
|
+
this.registry.delete(id);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return toRemove.length;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Gets registry statistics
|
|
260
|
+
* @returns Object containing registry stats
|
|
261
|
+
*/
|
|
262
|
+
getStats(): {
|
|
263
|
+
totalComponents: number;
|
|
264
|
+
namespaces: number;
|
|
265
|
+
totalRefCount: number;
|
|
266
|
+
oldestComponent?: Date;
|
|
267
|
+
newestComponent?: Date;
|
|
268
|
+
} {
|
|
269
|
+
let totalRefCount = 0;
|
|
270
|
+
let oldest: Date | undefined;
|
|
271
|
+
let newest: Date | undefined;
|
|
272
|
+
|
|
273
|
+
for (const entry of this.registry.values()) {
|
|
274
|
+
totalRefCount += entry.refCount;
|
|
275
|
+
|
|
276
|
+
if (!oldest || entry.metadata.registeredAt < oldest) {
|
|
277
|
+
oldest = entry.metadata.registeredAt;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!newest || entry.metadata.registeredAt > newest) {
|
|
281
|
+
newest = entry.metadata.registeredAt;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
totalComponents: this.registry.size,
|
|
287
|
+
namespaces: this.getNamespaces().length,
|
|
288
|
+
totalRefCount,
|
|
289
|
+
oldestComponent: oldest,
|
|
290
|
+
newestComponent: newest
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Destroys the registry and cleans up resources
|
|
296
|
+
*/
|
|
297
|
+
destroy(): void {
|
|
298
|
+
this.stopCleanupTimer();
|
|
299
|
+
this.clear();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Generates a unique registry key
|
|
304
|
+
* @param name - Component name
|
|
305
|
+
* @param namespace - Component namespace
|
|
306
|
+
* @param version - Component version
|
|
307
|
+
* @returns Registry key
|
|
308
|
+
*/
|
|
309
|
+
private generateRegistryKey(name: string, namespace: string, version: string): string {
|
|
310
|
+
if (this.config.enableNamespaces) {
|
|
311
|
+
return `${namespace}::${name}@${version}`;
|
|
312
|
+
}
|
|
313
|
+
return `${name}@${version}`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Finds the latest version of a component
|
|
318
|
+
* @param name - Component name
|
|
319
|
+
* @param namespace - Component namespace
|
|
320
|
+
* @returns Registry key of latest version or undefined
|
|
321
|
+
*/
|
|
322
|
+
private findLatestVersion(name: string, namespace: string): string | undefined {
|
|
323
|
+
let latestKey: string | undefined;
|
|
324
|
+
let latestDate: Date | undefined;
|
|
325
|
+
|
|
326
|
+
for (const [key, entry] of this.registry) {
|
|
327
|
+
if (entry.metadata.name === name &&
|
|
328
|
+
entry.metadata.namespace === namespace) {
|
|
329
|
+
if (!latestDate || entry.metadata.registeredAt > latestDate) {
|
|
330
|
+
latestDate = entry.metadata.registeredAt;
|
|
331
|
+
latestKey = key;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return latestKey;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Evicts the least recently used component
|
|
341
|
+
*/
|
|
342
|
+
private evictLRU(): void {
|
|
343
|
+
let lruKey: string | undefined;
|
|
344
|
+
let lruTime: Date | undefined;
|
|
345
|
+
|
|
346
|
+
for (const [key, entry] of this.registry) {
|
|
347
|
+
// Skip components with active references
|
|
348
|
+
if (entry.refCount > 0) continue;
|
|
349
|
+
|
|
350
|
+
if (!lruTime || entry.lastAccessed < lruTime) {
|
|
351
|
+
lruTime = entry.lastAccessed;
|
|
352
|
+
lruKey = key;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (lruKey) {
|
|
357
|
+
this.registry.delete(lruKey);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Starts the automatic cleanup timer
|
|
363
|
+
*/
|
|
364
|
+
private startCleanupTimer(): void {
|
|
365
|
+
this.cleanupTimer = setInterval(() => {
|
|
366
|
+
this.cleanup();
|
|
367
|
+
}, this.config.cleanupInterval);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Stops the automatic cleanup timer
|
|
372
|
+
*/
|
|
373
|
+
private stopCleanupTimer(): void {
|
|
374
|
+
if (this.cleanupTimer) {
|
|
375
|
+
clearInterval(this.cleanupTimer as any);
|
|
376
|
+
this.cleanupTimer = undefined;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Component dependency resolver for managing component relationships.
|
|
3
|
+
* Handles resolution of child components and dependency graphs.
|
|
4
|
+
* @module @memberjunction/react-runtime/registry
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ComponentRegistry } from './component-registry';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Component specification interface matching Skip component structure
|
|
11
|
+
*/
|
|
12
|
+
export interface ComponentSpec {
|
|
13
|
+
componentName: string;
|
|
14
|
+
componentCode?: string;
|
|
15
|
+
childComponents?: ComponentSpec[];
|
|
16
|
+
components?: ComponentSpec[]; // Alternative property name for children
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolved component map for passing to React components
|
|
21
|
+
*/
|
|
22
|
+
export interface ResolvedComponents {
|
|
23
|
+
[componentName: string]: any;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Component dependency resolver.
|
|
28
|
+
* Resolves component hierarchies and manages dependencies between components.
|
|
29
|
+
*/
|
|
30
|
+
export class ComponentResolver {
|
|
31
|
+
private registry: ComponentRegistry;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new ComponentResolver instance
|
|
35
|
+
* @param registry - Component registry to use for resolution
|
|
36
|
+
*/
|
|
37
|
+
constructor(registry: ComponentRegistry) {
|
|
38
|
+
this.registry = registry;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolves all components for a given component specification
|
|
43
|
+
* @param spec - Root component specification
|
|
44
|
+
* @param namespace - Namespace for component resolution
|
|
45
|
+
* @returns Map of component names to resolved components
|
|
46
|
+
*/
|
|
47
|
+
resolveComponents(spec: ComponentSpec, namespace: string = 'Global'): ResolvedComponents {
|
|
48
|
+
const resolved: ResolvedComponents = {};
|
|
49
|
+
|
|
50
|
+
// Resolve the component hierarchy
|
|
51
|
+
this.resolveComponentHierarchy(spec, resolved, namespace);
|
|
52
|
+
|
|
53
|
+
return resolved;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Recursively resolves a component hierarchy
|
|
58
|
+
* @param spec - Component specification
|
|
59
|
+
* @param resolved - Map to store resolved components
|
|
60
|
+
* @param namespace - Namespace for resolution
|
|
61
|
+
* @param visited - Set of visited component names to prevent cycles
|
|
62
|
+
*/
|
|
63
|
+
private resolveComponentHierarchy(
|
|
64
|
+
spec: ComponentSpec,
|
|
65
|
+
resolved: ResolvedComponents,
|
|
66
|
+
namespace: string,
|
|
67
|
+
visited: Set<string> = new Set()
|
|
68
|
+
): void {
|
|
69
|
+
// Prevent circular dependencies
|
|
70
|
+
if (visited.has(spec.componentName)) {
|
|
71
|
+
console.warn(`Circular dependency detected for component: ${spec.componentName}`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
visited.add(spec.componentName);
|
|
75
|
+
|
|
76
|
+
// Try to get component from registry
|
|
77
|
+
const component = this.registry.get(spec.componentName, namespace);
|
|
78
|
+
if (component) {
|
|
79
|
+
resolved[spec.componentName] = component;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Process child components (handle both property names)
|
|
83
|
+
const children = spec.childComponents || spec.components || [];
|
|
84
|
+
for (const child of children) {
|
|
85
|
+
this.resolveComponentHierarchy(child, resolved, namespace, visited);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Validates that all required components are available
|
|
91
|
+
* @param spec - Component specification to validate
|
|
92
|
+
* @param namespace - Namespace for validation
|
|
93
|
+
* @returns Array of missing component names
|
|
94
|
+
*/
|
|
95
|
+
validateDependencies(spec: ComponentSpec, namespace: string = 'Global'): string[] {
|
|
96
|
+
const missing: string[] = [];
|
|
97
|
+
const checked = new Set<string>();
|
|
98
|
+
|
|
99
|
+
this.checkDependencies(spec, namespace, missing, checked);
|
|
100
|
+
|
|
101
|
+
return missing;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Recursively checks for missing dependencies
|
|
106
|
+
* @param spec - Component specification
|
|
107
|
+
* @param namespace - Namespace for checking
|
|
108
|
+
* @param missing - Array to collect missing components
|
|
109
|
+
* @param checked - Set of already checked components
|
|
110
|
+
*/
|
|
111
|
+
private checkDependencies(
|
|
112
|
+
spec: ComponentSpec,
|
|
113
|
+
namespace: string,
|
|
114
|
+
missing: string[],
|
|
115
|
+
checked: Set<string>
|
|
116
|
+
): void {
|
|
117
|
+
if (checked.has(spec.componentName)) return;
|
|
118
|
+
checked.add(spec.componentName);
|
|
119
|
+
|
|
120
|
+
// Check if component exists in registry
|
|
121
|
+
if (!this.registry.has(spec.componentName, namespace)) {
|
|
122
|
+
missing.push(spec.componentName);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check children
|
|
126
|
+
const children = spec.childComponents || spec.components || [];
|
|
127
|
+
for (const child of children) {
|
|
128
|
+
this.checkDependencies(child, namespace, missing, checked);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Gets the dependency graph for a component specification
|
|
134
|
+
* @param spec - Component specification
|
|
135
|
+
* @returns Dependency graph as adjacency list
|
|
136
|
+
*/
|
|
137
|
+
getDependencyGraph(spec: ComponentSpec): Map<string, string[]> {
|
|
138
|
+
const graph = new Map<string, string[]>();
|
|
139
|
+
const visited = new Set<string>();
|
|
140
|
+
|
|
141
|
+
this.buildDependencyGraph(spec, graph, visited);
|
|
142
|
+
|
|
143
|
+
return graph;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Recursively builds the dependency graph
|
|
148
|
+
* @param spec - Component specification
|
|
149
|
+
* @param graph - Graph to build
|
|
150
|
+
* @param visited - Set of visited components
|
|
151
|
+
*/
|
|
152
|
+
private buildDependencyGraph(
|
|
153
|
+
spec: ComponentSpec,
|
|
154
|
+
graph: Map<string, string[]>,
|
|
155
|
+
visited: Set<string>
|
|
156
|
+
): void {
|
|
157
|
+
if (visited.has(spec.componentName)) return;
|
|
158
|
+
visited.add(spec.componentName);
|
|
159
|
+
|
|
160
|
+
const children = spec.childComponents || spec.components || [];
|
|
161
|
+
const dependencies = children.map(child => child.componentName);
|
|
162
|
+
|
|
163
|
+
graph.set(spec.componentName, dependencies);
|
|
164
|
+
|
|
165
|
+
// Recursively process children
|
|
166
|
+
for (const child of children) {
|
|
167
|
+
this.buildDependencyGraph(child, graph, visited);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Performs topological sort on component dependencies
|
|
173
|
+
* @param spec - Root component specification
|
|
174
|
+
* @returns Array of component names in dependency order
|
|
175
|
+
*/
|
|
176
|
+
getLoadOrder(spec: ComponentSpec): string[] {
|
|
177
|
+
const graph = this.getDependencyGraph(spec);
|
|
178
|
+
const visited = new Set<string>();
|
|
179
|
+
const stack: string[] = [];
|
|
180
|
+
|
|
181
|
+
// Perform DFS on all nodes
|
|
182
|
+
for (const node of graph.keys()) {
|
|
183
|
+
if (!visited.has(node)) {
|
|
184
|
+
this.topologicalSortDFS(node, graph, visited, stack);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Reverse to get correct load order
|
|
189
|
+
return stack.reverse();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* DFS helper for topological sort
|
|
194
|
+
* @param node - Current node
|
|
195
|
+
* @param graph - Dependency graph
|
|
196
|
+
* @param visited - Set of visited nodes
|
|
197
|
+
* @param stack - Result stack
|
|
198
|
+
*/
|
|
199
|
+
private topologicalSortDFS(
|
|
200
|
+
node: string,
|
|
201
|
+
graph: Map<string, string[]>,
|
|
202
|
+
visited: Set<string>,
|
|
203
|
+
stack: string[]
|
|
204
|
+
): void {
|
|
205
|
+
visited.add(node);
|
|
206
|
+
|
|
207
|
+
const dependencies = graph.get(node) || [];
|
|
208
|
+
for (const dep of dependencies) {
|
|
209
|
+
if (!visited.has(dep)) {
|
|
210
|
+
this.topologicalSortDFS(dep, graph, visited, stack);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
stack.push(node);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Resolves components in the correct dependency order
|
|
219
|
+
* @param spec - Root component specification
|
|
220
|
+
* @param namespace - Namespace for resolution
|
|
221
|
+
* @returns Ordered array of resolved components
|
|
222
|
+
*/
|
|
223
|
+
resolveInOrder(spec: ComponentSpec, namespace: string = 'Global'): Array<{
|
|
224
|
+
name: string;
|
|
225
|
+
component: any;
|
|
226
|
+
}> {
|
|
227
|
+
const loadOrder = this.getLoadOrder(spec);
|
|
228
|
+
const resolved: Array<{ name: string; component: any }> = [];
|
|
229
|
+
|
|
230
|
+
for (const name of loadOrder) {
|
|
231
|
+
const component = this.registry.get(name, namespace);
|
|
232
|
+
if (component) {
|
|
233
|
+
resolved.push({ name, component });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return resolved;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Creates a flattened list of all component specifications
|
|
242
|
+
* @param spec - Root component specification
|
|
243
|
+
* @returns Array of all component specs in the hierarchy
|
|
244
|
+
*/
|
|
245
|
+
flattenComponentSpecs(spec: ComponentSpec): ComponentSpec[] {
|
|
246
|
+
const flattened: ComponentSpec[] = [];
|
|
247
|
+
const visited = new Set<string>();
|
|
248
|
+
|
|
249
|
+
this.collectComponentSpecs(spec, flattened, visited);
|
|
250
|
+
|
|
251
|
+
return flattened;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Recursively collects component specifications
|
|
256
|
+
* @param spec - Current component specification
|
|
257
|
+
* @param collected - Array to collect specs
|
|
258
|
+
* @param visited - Set of visited component names
|
|
259
|
+
*/
|
|
260
|
+
private collectComponentSpecs(
|
|
261
|
+
spec: ComponentSpec,
|
|
262
|
+
collected: ComponentSpec[],
|
|
263
|
+
visited: Set<string>
|
|
264
|
+
): void {
|
|
265
|
+
if (visited.has(spec.componentName)) return;
|
|
266
|
+
visited.add(spec.componentName);
|
|
267
|
+
|
|
268
|
+
collected.push(spec);
|
|
269
|
+
|
|
270
|
+
const children = spec.childComponents || spec.components || [];
|
|
271
|
+
for (const child of children) {
|
|
272
|
+
this.collectComponentSpecs(child, collected, visited);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|