typekro 0.2.2 → 0.3.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/README.md +4 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/core/composition/imperative.d.ts.map +1 -1
- package/dist/core/composition/imperative.js +15 -2
- package/dist/core/composition/imperative.js.map +1 -1
- package/dist/core/composition/typekro-runtime/typekro-runtime.d.ts.map +1 -1
- package/dist/core/composition/typekro-runtime/typekro-runtime.js +24 -25
- package/dist/core/composition/typekro-runtime/typekro-runtime.js.map +1 -1
- package/dist/core/dependencies/type-guards.d.ts.map +1 -1
- package/dist/core/dependencies/type-guards.js +7 -2
- package/dist/core/dependencies/type-guards.js.map +1 -1
- package/dist/core/deployment/engine.d.ts +0 -1
- package/dist/core/deployment/engine.d.ts.map +1 -1
- package/dist/core/deployment/engine.js +0 -1
- package/dist/core/deployment/engine.js.map +1 -1
- package/dist/core/errors.d.ts +85 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +135 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/expressions/analyzer.d.ts +584 -0
- package/dist/core/expressions/analyzer.d.ts.map +1 -0
- package/dist/core/expressions/analyzer.js +2956 -0
- package/dist/core/expressions/analyzer.js.map +1 -0
- package/dist/core/expressions/cache.d.ts +136 -0
- package/dist/core/expressions/cache.d.ts.map +1 -0
- package/dist/core/expressions/cache.js +347 -0
- package/dist/core/expressions/cache.js.map +1 -0
- package/dist/core/expressions/cel-conversion-engine.d.ts +126 -0
- package/dist/core/expressions/cel-conversion-engine.d.ts.map +1 -0
- package/dist/core/expressions/cel-conversion-engine.js +293 -0
- package/dist/core/expressions/cel-conversion-engine.js.map +1 -0
- package/dist/core/expressions/compile-time-validation.d.ts +270 -0
- package/dist/core/expressions/compile-time-validation.d.ts.map +1 -0
- package/dist/core/expressions/compile-time-validation.js +506 -0
- package/dist/core/expressions/compile-time-validation.js.map +1 -0
- package/dist/core/expressions/composition-integration.d.ts +315 -0
- package/dist/core/expressions/composition-integration.d.ts.map +1 -0
- package/dist/core/expressions/composition-integration.js +936 -0
- package/dist/core/expressions/composition-integration.js.map +1 -0
- package/dist/core/expressions/conditional-expression-processor.d.ts +154 -0
- package/dist/core/expressions/conditional-expression-processor.d.ts.map +1 -0
- package/dist/core/expressions/conditional-expression-processor.js +479 -0
- package/dist/core/expressions/conditional-expression-processor.js.map +1 -0
- package/dist/core/expressions/conditional-integration.d.ts +133 -0
- package/dist/core/expressions/conditional-integration.d.ts.map +1 -0
- package/dist/core/expressions/conditional-integration.js +293 -0
- package/dist/core/expressions/conditional-integration.js.map +1 -0
- package/dist/core/expressions/conditional-validation.d.ts +181 -0
- package/dist/core/expressions/conditional-validation.d.ts.map +1 -0
- package/dist/core/expressions/conditional-validation.js +460 -0
- package/dist/core/expressions/conditional-validation.js.map +1 -0
- package/dist/core/expressions/context-aware-generator.d.ts +127 -0
- package/dist/core/expressions/context-aware-generator.d.ts.map +1 -0
- package/dist/core/expressions/context-aware-generator.js +500 -0
- package/dist/core/expressions/context-aware-generator.js.map +1 -0
- package/dist/core/expressions/context-detector.d.ts +148 -0
- package/dist/core/expressions/context-detector.d.ts.map +1 -0
- package/dist/core/expressions/context-detector.js +546 -0
- package/dist/core/expressions/context-detector.js.map +1 -0
- package/dist/core/expressions/context-switcher.d.ts +185 -0
- package/dist/core/expressions/context-switcher.d.ts.map +1 -0
- package/dist/core/expressions/context-switcher.js +515 -0
- package/dist/core/expressions/context-switcher.js.map +1 -0
- package/dist/core/expressions/context-validator.d.ts +176 -0
- package/dist/core/expressions/context-validator.d.ts.map +1 -0
- package/dist/core/expressions/context-validator.js +452 -0
- package/dist/core/expressions/context-validator.js.map +1 -0
- package/dist/core/expressions/custom-context-manager.d.ts +194 -0
- package/dist/core/expressions/custom-context-manager.d.ts.map +1 -0
- package/dist/core/expressions/custom-context-manager.js +390 -0
- package/dist/core/expressions/custom-context-manager.js.map +1 -0
- package/dist/core/expressions/expression-proxy.d.ts +80 -0
- package/dist/core/expressions/expression-proxy.d.ts.map +1 -0
- package/dist/core/expressions/expression-proxy.js +227 -0
- package/dist/core/expressions/expression-proxy.js.map +1 -0
- package/dist/core/expressions/factory-integration.d.ts +132 -0
- package/dist/core/expressions/factory-integration.d.ts.map +1 -0
- package/dist/core/expressions/factory-integration.js +327 -0
- package/dist/core/expressions/factory-integration.js.map +1 -0
- package/dist/core/expressions/factory-pattern-handler.d.ts +88 -0
- package/dist/core/expressions/factory-pattern-handler.d.ts.map +1 -0
- package/dist/core/expressions/factory-pattern-handler.js +336 -0
- package/dist/core/expressions/factory-pattern-handler.js.map +1 -0
- package/dist/core/expressions/field-hydration-processor.d.ts +188 -0
- package/dist/core/expressions/field-hydration-processor.d.ts.map +1 -0
- package/dist/core/expressions/field-hydration-processor.js +562 -0
- package/dist/core/expressions/field-hydration-processor.js.map +1 -0
- package/dist/core/expressions/imperative-analyzer.d.ts +21 -0
- package/dist/core/expressions/imperative-analyzer.d.ts.map +1 -0
- package/dist/core/expressions/imperative-analyzer.js +343 -0
- package/dist/core/expressions/imperative-analyzer.js.map +1 -0
- package/dist/core/expressions/index.d.ts +54 -0
- package/dist/core/expressions/index.d.ts.map +1 -0
- package/dist/core/expressions/index.js +50 -0
- package/dist/core/expressions/index.js.map +1 -0
- package/dist/core/expressions/lazy-analysis.d.ts +1128 -0
- package/dist/core/expressions/lazy-analysis.d.ts.map +1 -0
- package/dist/core/expressions/lazy-analysis.js +2443 -0
- package/dist/core/expressions/lazy-analysis.js.map +1 -0
- package/dist/core/expressions/magic-assignable-analyzer.d.ts +123 -0
- package/dist/core/expressions/magic-assignable-analyzer.d.ts.map +1 -0
- package/dist/core/expressions/magic-assignable-analyzer.js +352 -0
- package/dist/core/expressions/magic-assignable-analyzer.js.map +1 -0
- package/dist/core/expressions/magic-proxy-analyzer.d.ts +206 -0
- package/dist/core/expressions/magic-proxy-analyzer.d.ts.map +1 -0
- package/dist/core/expressions/magic-proxy-analyzer.js +639 -0
- package/dist/core/expressions/magic-proxy-analyzer.js.map +1 -0
- package/dist/core/expressions/magic-proxy-detector.d.ts +154 -0
- package/dist/core/expressions/magic-proxy-detector.d.ts.map +1 -0
- package/dist/core/expressions/magic-proxy-detector.js +242 -0
- package/dist/core/expressions/magic-proxy-detector.js.map +1 -0
- package/dist/core/expressions/migration-helpers.d.ts +133 -0
- package/dist/core/expressions/migration-helpers.d.ts.map +1 -0
- package/dist/core/expressions/migration-helpers.js +443 -0
- package/dist/core/expressions/migration-helpers.js.map +1 -0
- package/dist/core/expressions/optionality-handler.d.ts +503 -0
- package/dist/core/expressions/optionality-handler.d.ts.map +1 -0
- package/dist/core/expressions/optionality-handler.js +1306 -0
- package/dist/core/expressions/optionality-handler.js.map +1 -0
- package/dist/core/expressions/readiness-integration.d.ts +119 -0
- package/dist/core/expressions/readiness-integration.d.ts.map +1 -0
- package/dist/core/expressions/readiness-integration.js +386 -0
- package/dist/core/expressions/readiness-integration.js.map +1 -0
- package/dist/core/expressions/resource-analyzer.d.ts +486 -0
- package/dist/core/expressions/resource-analyzer.d.ts.map +1 -0
- package/dist/core/expressions/resource-analyzer.js +1086 -0
- package/dist/core/expressions/resource-analyzer.js.map +1 -0
- package/dist/core/expressions/resource-validation.d.ts +187 -0
- package/dist/core/expressions/resource-validation.d.ts.map +1 -0
- package/dist/core/expressions/resource-validation.js +552 -0
- package/dist/core/expressions/resource-validation.js.map +1 -0
- package/dist/core/expressions/runtime-error-mapper.d.ts +138 -0
- package/dist/core/expressions/runtime-error-mapper.d.ts.map +1 -0
- package/dist/core/expressions/runtime-error-mapper.js +412 -0
- package/dist/core/expressions/runtime-error-mapper.js.map +1 -0
- package/dist/core/expressions/source-map.d.ts +168 -0
- package/dist/core/expressions/source-map.d.ts.map +1 -0
- package/dist/core/expressions/source-map.js +350 -0
- package/dist/core/expressions/source-map.js.map +1 -0
- package/dist/core/expressions/status-builder-analyzer.d.ts +353 -0
- package/dist/core/expressions/status-builder-analyzer.d.ts.map +1 -0
- package/dist/core/expressions/status-builder-analyzer.js +1301 -0
- package/dist/core/expressions/status-builder-analyzer.js.map +1 -0
- package/dist/core/expressions/type-inference.d.ts +184 -0
- package/dist/core/expressions/type-inference.d.ts.map +1 -0
- package/dist/core/expressions/type-inference.js +838 -0
- package/dist/core/expressions/type-inference.js.map +1 -0
- package/dist/core/expressions/type-safety.d.ts +203 -0
- package/dist/core/expressions/type-safety.d.ts.map +1 -0
- package/dist/core/expressions/type-safety.js +442 -0
- package/dist/core/expressions/type-safety.js.map +1 -0
- package/dist/core/expressions/types.d.ts +282 -0
- package/dist/core/expressions/types.d.ts.map +1 -0
- package/dist/core/expressions/types.js +8 -0
- package/dist/core/expressions/types.js.map +1 -0
- package/dist/core/kubernetes/client-provider.js +2 -2
- package/dist/core/kubernetes/client-provider.js.map +1 -1
- package/dist/core/serialization/core.d.ts.map +1 -1
- package/dist/core/serialization/core.js +573 -9
- package/dist/core/serialization/core.js.map +1 -1
- package/dist/core/types/deployment.d.ts +4 -0
- package/dist/core/types/deployment.d.ts.map +1 -1
- package/dist/core/types/deployment.js.map +1 -1
- package/dist/core/types/index.d.ts +1 -0
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/core/types/index.js.map +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/factories/helm/helm-release.d.ts.map +1 -1
- package/dist/factories/helm/helm-release.js +0 -5
- package/dist/factories/helm/helm-release.js.map +1 -1
- package/dist/factories/helm/types.d.ts +1 -1
- package/dist/factories/helm/types.d.ts.map +1 -1
- package/dist/factories/shared.d.ts.map +1 -1
- package/dist/factories/shared.js +21 -1
- package/dist/factories/shared.js.map +1 -1
- package/dist/factories/simple/index.d.ts +2 -2
- package/dist/factories/simple/index.d.ts.map +1 -1
- package/dist/factories/simple/workloads/deployment.d.ts +3 -3
- package/dist/factories/simple/workloads/deployment.d.ts.map +1 -1
- package/dist/factories/simple/workloads/deployment.js +37 -11
- package/dist/factories/simple/workloads/deployment.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/type-guards.d.ts +6 -0
- package/dist/utils/type-guards.d.ts.map +1 -1
- package/dist/utils/type-guards.js +25 -2
- package/dist/utils/type-guards.js.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composition Integration for JavaScript to CEL Expression Conversion
|
|
3
|
+
*
|
|
4
|
+
* This module provides integration between the kubernetesComposition API and the
|
|
5
|
+
* JavaScript to CEL expression conversion system. It handles KubernetesRef detection
|
|
6
|
+
* and conversion within imperative composition patterns.
|
|
7
|
+
*/
|
|
8
|
+
import { getCurrentCompositionContext } from '../../factories/shared.js';
|
|
9
|
+
import { isKubernetesRef } from '../../utils/type-guards.js';
|
|
10
|
+
import { MagicAssignableAnalyzer } from './magic-assignable-analyzer.js';
|
|
11
|
+
import { CelConversionEngine } from './cel-conversion-engine.js';
|
|
12
|
+
/**
|
|
13
|
+
* Composition-aware expression analyzer that integrates with kubernetesComposition
|
|
14
|
+
*/
|
|
15
|
+
export class CompositionExpressionAnalyzer {
|
|
16
|
+
magicAssignableAnalyzer;
|
|
17
|
+
celEngine;
|
|
18
|
+
patternConfigs;
|
|
19
|
+
contextTracker;
|
|
20
|
+
scopeManager;
|
|
21
|
+
constructor() {
|
|
22
|
+
this.magicAssignableAnalyzer = new MagicAssignableAnalyzer();
|
|
23
|
+
this.celEngine = new CelConversionEngine();
|
|
24
|
+
this.contextTracker = new CompositionContextTracker();
|
|
25
|
+
this.scopeManager = new MagicProxyScopeManager();
|
|
26
|
+
// Initialize pattern-specific configurations
|
|
27
|
+
this.patternConfigs = new Map([
|
|
28
|
+
['imperative', {
|
|
29
|
+
pattern: 'imperative',
|
|
30
|
+
allowSideEffects: true,
|
|
31
|
+
trackResourceCreation: true,
|
|
32
|
+
validateScope: true,
|
|
33
|
+
convertTocel: true,
|
|
34
|
+
}],
|
|
35
|
+
['declarative', {
|
|
36
|
+
pattern: 'declarative',
|
|
37
|
+
allowSideEffects: false,
|
|
38
|
+
trackResourceCreation: false,
|
|
39
|
+
validateScope: false,
|
|
40
|
+
convertTocel: true,
|
|
41
|
+
}],
|
|
42
|
+
]);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Detect the composition pattern being used
|
|
46
|
+
*/
|
|
47
|
+
detectCompositionPattern(compositionFn, context) {
|
|
48
|
+
// If there's an active composition context, it's imperative
|
|
49
|
+
if (context || getCurrentCompositionContext()) {
|
|
50
|
+
return 'imperative';
|
|
51
|
+
}
|
|
52
|
+
// Analyze function signature and behavior
|
|
53
|
+
const fnString = compositionFn.toString();
|
|
54
|
+
// Look for imperative patterns
|
|
55
|
+
const imperativeIndicators = [
|
|
56
|
+
/\.add\w+\(/, // .addResource, .addService, etc.
|
|
57
|
+
/register\w+\(/, // registerResource, etc.
|
|
58
|
+
/create\w+\(/, // createResource, etc.
|
|
59
|
+
/simple\w+\(/, // simpleDeployment, simpleService, etc.
|
|
60
|
+
];
|
|
61
|
+
const hasImperativeIndicators = imperativeIndicators.some(pattern => pattern.test(fnString));
|
|
62
|
+
return hasImperativeIndicators ? 'imperative' : 'declarative';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Analyze composition function with pattern awareness
|
|
66
|
+
*/
|
|
67
|
+
analyzeCompositionFunctionWithPattern(compositionFn, schemaProxy, pattern, context) {
|
|
68
|
+
// Detect pattern if not provided
|
|
69
|
+
const detectedPattern = pattern || this.detectCompositionPattern(compositionFn, context);
|
|
70
|
+
const config = this.patternConfigs.get(detectedPattern);
|
|
71
|
+
// Track side effects if this is an imperative pattern
|
|
72
|
+
let sideEffectsDetected = false;
|
|
73
|
+
let resourceCreationTracked = false;
|
|
74
|
+
let scopeValidationPerformed = false;
|
|
75
|
+
if (config.trackResourceCreation && context) {
|
|
76
|
+
const resourcesBefore = new Set(Object.keys(context.resources));
|
|
77
|
+
// Execute the base analysis
|
|
78
|
+
const baseResult = this.analyzeCompositionFunction(compositionFn, schemaProxy, context);
|
|
79
|
+
// Check for side effects
|
|
80
|
+
const resourcesAfter = Object.keys(context.resources);
|
|
81
|
+
sideEffectsDetected = resourcesAfter.some(id => !resourcesBefore.has(id));
|
|
82
|
+
resourceCreationTracked = true;
|
|
83
|
+
// Perform scope validation if enabled
|
|
84
|
+
if (config.validateScope) {
|
|
85
|
+
// This would be handled by the scope manager
|
|
86
|
+
scopeValidationPerformed = true;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...baseResult,
|
|
90
|
+
pattern: detectedPattern,
|
|
91
|
+
patternSpecificMetadata: {
|
|
92
|
+
sideEffectsDetected,
|
|
93
|
+
resourceCreationTracked,
|
|
94
|
+
scopeValidationPerformed,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Execute the base analysis without side effect tracking
|
|
100
|
+
const baseResult = this.analyzeCompositionFunction(compositionFn, schemaProxy, context);
|
|
101
|
+
return {
|
|
102
|
+
...baseResult,
|
|
103
|
+
pattern: detectedPattern,
|
|
104
|
+
patternSpecificMetadata: {
|
|
105
|
+
sideEffectsDetected,
|
|
106
|
+
resourceCreationTracked,
|
|
107
|
+
scopeValidationPerformed,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Process composition based on detected pattern
|
|
114
|
+
*/
|
|
115
|
+
processCompositionByPattern(statusShape, pattern, factoryType = 'kro') {
|
|
116
|
+
const config = this.patternConfigs.get(pattern);
|
|
117
|
+
if (!config.convertTocel) {
|
|
118
|
+
// Pattern doesn't require CEL conversion
|
|
119
|
+
return statusShape;
|
|
120
|
+
}
|
|
121
|
+
// Use the standard processing logic
|
|
122
|
+
return this.processCompositionStatus(statusShape, factoryType);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Validate composition pattern compatibility
|
|
126
|
+
*/
|
|
127
|
+
validatePatternCompatibility(pattern, factoryType, context) {
|
|
128
|
+
const warnings = [];
|
|
129
|
+
const recommendations = [];
|
|
130
|
+
let isCompatible = true;
|
|
131
|
+
const config = this.patternConfigs.get(pattern);
|
|
132
|
+
// Check imperative pattern with direct factory
|
|
133
|
+
if (pattern === 'imperative' && factoryType === 'direct') {
|
|
134
|
+
if (!context) {
|
|
135
|
+
warnings.push('Imperative pattern without composition context may not work correctly with direct factory');
|
|
136
|
+
recommendations.push('Ensure kubernetesComposition is used with proper context management');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Check declarative pattern with side effects
|
|
140
|
+
if (pattern === 'declarative' && config.allowSideEffects && context) {
|
|
141
|
+
const resourceCount = Object.keys(context.resources).length;
|
|
142
|
+
if (resourceCount > 0) {
|
|
143
|
+
warnings.push('Declarative pattern detected but resources found in composition context');
|
|
144
|
+
recommendations.push('Consider using imperative pattern (kubernetesComposition) for side-effect based resource creation');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Check CEL conversion compatibility
|
|
148
|
+
if (factoryType === 'kro' && !config.convertTocel) {
|
|
149
|
+
isCompatible = false;
|
|
150
|
+
warnings.push(`Pattern '${pattern}' is not compatible with Kro factory (CEL conversion required)`);
|
|
151
|
+
recommendations.push('Use direct factory or enable CEL conversion for this pattern');
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
isCompatible,
|
|
155
|
+
warnings,
|
|
156
|
+
recommendations,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get pattern-specific analysis recommendations
|
|
161
|
+
*/
|
|
162
|
+
getPatternRecommendations(pattern, analysisResult) {
|
|
163
|
+
const recommendations = [];
|
|
164
|
+
if (pattern === 'imperative') {
|
|
165
|
+
if (analysisResult.kubernetesRefs.length === 0) {
|
|
166
|
+
recommendations.push('Consider using declarative pattern (toResourceGraph) for static compositions without KubernetesRef objects');
|
|
167
|
+
}
|
|
168
|
+
if (analysisResult.referencedResources.length > 10) {
|
|
169
|
+
recommendations.push('Large number of resource references detected - consider breaking into smaller compositions');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (pattern === 'declarative') {
|
|
173
|
+
if (analysisResult.kubernetesRefs.length > 0) {
|
|
174
|
+
recommendations.push('KubernetesRef objects detected - imperative pattern (kubernetesComposition) might be more suitable');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return recommendations;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Analyze a composition function for KubernetesRef usage and expression conversion needs
|
|
181
|
+
*/
|
|
182
|
+
analyzeCompositionFunction(compositionFn, schemaProxy, context) {
|
|
183
|
+
const _startTime = Date.now();
|
|
184
|
+
try {
|
|
185
|
+
// Execute the composition function to capture the returned status shape
|
|
186
|
+
const statusShape = compositionFn(schemaProxy.spec);
|
|
187
|
+
// Create analysis context
|
|
188
|
+
const analysisContext = {
|
|
189
|
+
type: 'status',
|
|
190
|
+
availableReferences: context?.resources || {},
|
|
191
|
+
schemaProxy,
|
|
192
|
+
factoryType: 'kro',
|
|
193
|
+
};
|
|
194
|
+
// Analyze the status shape for KubernetesRef objects
|
|
195
|
+
const analysisResult = this.magicAssignableAnalyzer.analyzeMagicAssignableShape(statusShape, analysisContext);
|
|
196
|
+
// Extract referenced resources from the composition context if available
|
|
197
|
+
const referencedResources = this.extractReferencedResources(context);
|
|
198
|
+
// Determine if CEL conversion is needed
|
|
199
|
+
const requiresCelConversion = analysisResult.dependencies.length > 0;
|
|
200
|
+
const conversionMetadata = {
|
|
201
|
+
expressionsAnalyzed: Object.keys(analysisResult.fieldResults).length,
|
|
202
|
+
kubernetesRefsDetected: analysisResult.dependencies.length,
|
|
203
|
+
celExpressionsGenerated: requiresCelConversion ? Object.keys(analysisResult.fieldResults).length : 0,
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
statusShape,
|
|
207
|
+
kubernetesRefs: analysisResult.dependencies,
|
|
208
|
+
referencedResources,
|
|
209
|
+
requiresCelConversion,
|
|
210
|
+
conversionMetadata,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
throw new Error(`Failed to analyze composition function: ${error instanceof Error ? error.message : String(error)}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Analyze a composition function's resource creation patterns
|
|
219
|
+
*/
|
|
220
|
+
analyzeResourceCreation(compositionFn, schemaProxy, context) {
|
|
221
|
+
const currentContext = context || getCurrentCompositionContext();
|
|
222
|
+
if (!currentContext) {
|
|
223
|
+
return {
|
|
224
|
+
resourcesCreated: [],
|
|
225
|
+
kubernetesRefsInResources: [],
|
|
226
|
+
requiresCelConversion: false,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// Track resources before execution
|
|
230
|
+
const resourcesBefore = new Set(Object.keys(currentContext.resources));
|
|
231
|
+
try {
|
|
232
|
+
// Execute the composition function to trigger resource creation
|
|
233
|
+
compositionFn(schemaProxy.spec);
|
|
234
|
+
// Find newly created resources
|
|
235
|
+
const resourcesAfter = Object.keys(currentContext.resources);
|
|
236
|
+
const resourcesCreated = resourcesAfter.filter(id => !resourcesBefore.has(id));
|
|
237
|
+
// Analyze newly created resources for KubernetesRef objects
|
|
238
|
+
const kubernetesRefsInResources = [];
|
|
239
|
+
for (const resourceId of resourcesCreated) {
|
|
240
|
+
const resource = currentContext.resources[resourceId];
|
|
241
|
+
if (resource) {
|
|
242
|
+
const refs = this.extractKubernetesRefsFromResource(resource);
|
|
243
|
+
kubernetesRefsInResources.push(...refs);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
resourcesCreated,
|
|
248
|
+
kubernetesRefsInResources,
|
|
249
|
+
requiresCelConversion: kubernetesRefsInResources.length > 0,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
throw new Error(`Failed to analyze resource creation: ${error instanceof Error ? error.message : String(error)}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Process a composition's status shape for CEL conversion
|
|
258
|
+
*/
|
|
259
|
+
processCompositionStatus(statusShape, factoryType = 'kro') {
|
|
260
|
+
if (factoryType === 'direct') {
|
|
261
|
+
// For direct factory, leave expressions as-is for runtime evaluation
|
|
262
|
+
return statusShape;
|
|
263
|
+
}
|
|
264
|
+
// Create analysis context
|
|
265
|
+
const analysisContext = {
|
|
266
|
+
type: 'status',
|
|
267
|
+
availableReferences: {},
|
|
268
|
+
factoryType,
|
|
269
|
+
};
|
|
270
|
+
// For Kro factory, convert KubernetesRef-containing expressions to CEL
|
|
271
|
+
return this.magicAssignableAnalyzer.analyzeMagicAssignableShape(statusShape, analysisContext).processedShape;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Enhanced status building with comprehensive KubernetesRef handling
|
|
275
|
+
*/
|
|
276
|
+
buildCompositionStatus(statusShape, context, factoryType = 'kro') {
|
|
277
|
+
// Create analysis context
|
|
278
|
+
const analysisContext = {
|
|
279
|
+
type: 'status',
|
|
280
|
+
availableReferences: context.resources,
|
|
281
|
+
factoryType,
|
|
282
|
+
};
|
|
283
|
+
// Analyze the status shape for KubernetesRef objects
|
|
284
|
+
const analysisResult = this.magicAssignableAnalyzer.analyzeMagicAssignableShape(statusShape, analysisContext);
|
|
285
|
+
// Track context for dependency analysis
|
|
286
|
+
const contextTracking = this.contextTracker.trackCompositionContext(context);
|
|
287
|
+
// Extract dependencies from KubernetesRef objects
|
|
288
|
+
const dependencies = new Set();
|
|
289
|
+
let crossResourceReferences = 0;
|
|
290
|
+
for (const ref of analysisResult.dependencies) {
|
|
291
|
+
if (ref.resourceId !== '__schema__') {
|
|
292
|
+
dependencies.add(ref.resourceId);
|
|
293
|
+
// Check if this is a cross-resource reference
|
|
294
|
+
if (contextTracking.resourcesWithKubernetesRefs.includes(ref.resourceId)) {
|
|
295
|
+
crossResourceReferences++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Process the status shape based on factory type
|
|
300
|
+
let processedStatus;
|
|
301
|
+
let celExpressionsGenerated = 0;
|
|
302
|
+
if (factoryType === 'direct') {
|
|
303
|
+
// For direct factory, leave expressions as-is for runtime evaluation
|
|
304
|
+
processedStatus = statusShape;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// For Kro factory, convert KubernetesRef-containing expressions to CEL
|
|
308
|
+
processedStatus = analysisResult.processedShape;
|
|
309
|
+
celExpressionsGenerated = Object.keys(analysisResult.fieldResults).length;
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
processedStatus,
|
|
313
|
+
kubernetesRefs: analysisResult.dependencies,
|
|
314
|
+
dependencies: Array.from(dependencies),
|
|
315
|
+
requiresCelConversion: analysisResult.dependencies.length > 0,
|
|
316
|
+
conversionMetadata: {
|
|
317
|
+
fieldsProcessed: Object.keys(analysisResult.fieldResults).length,
|
|
318
|
+
kubernetesRefsFound: analysisResult.dependencies.length,
|
|
319
|
+
celExpressionsGenerated,
|
|
320
|
+
crossResourceReferences,
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Validate status shape for composition compatibility
|
|
326
|
+
*/
|
|
327
|
+
validateStatusShape(statusShape, context) {
|
|
328
|
+
const errors = [];
|
|
329
|
+
const warnings = [];
|
|
330
|
+
try {
|
|
331
|
+
// Create analysis context
|
|
332
|
+
const analysisContext = {
|
|
333
|
+
type: 'status',
|
|
334
|
+
availableReferences: context?.resources || {},
|
|
335
|
+
factoryType: 'kro',
|
|
336
|
+
};
|
|
337
|
+
// Analyze the status shape
|
|
338
|
+
const analysisResult = this.magicAssignableAnalyzer.analyzeMagicAssignableShape(statusShape, analysisContext);
|
|
339
|
+
// Validate each KubernetesRef
|
|
340
|
+
for (const ref of analysisResult.dependencies) {
|
|
341
|
+
const scopeValidation = this.scopeManager.validateKubernetesRefScope(ref);
|
|
342
|
+
if (!scopeValidation.isValid) {
|
|
343
|
+
errors.push(`Invalid KubernetesRef scope: ${scopeValidation.error}`);
|
|
344
|
+
}
|
|
345
|
+
// Check if the referenced resource exists in the context
|
|
346
|
+
if (context && ref.resourceId !== '__schema__' && !context.resources[ref.resourceId]) {
|
|
347
|
+
warnings.push(`Referenced resource '${ref.resourceId}' not found in composition context`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Check for circular dependencies
|
|
351
|
+
if (context) {
|
|
352
|
+
const contextTracking = this.contextTracker.trackCompositionContext(context);
|
|
353
|
+
// Simple circular dependency detection
|
|
354
|
+
const resourceGraph = new Map();
|
|
355
|
+
for (const crossRef of contextTracking.crossResourceReferences) {
|
|
356
|
+
if (!resourceGraph.has(crossRef.sourceResource)) {
|
|
357
|
+
resourceGraph.set(crossRef.sourceResource, new Set());
|
|
358
|
+
}
|
|
359
|
+
resourceGraph.get(crossRef.sourceResource)?.add(crossRef.targetResource);
|
|
360
|
+
}
|
|
361
|
+
// Check for cycles using DFS
|
|
362
|
+
const visited = new Set();
|
|
363
|
+
const recursionStack = new Set();
|
|
364
|
+
const hasCycle = (node) => {
|
|
365
|
+
if (recursionStack.has(node)) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
if (visited.has(node)) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
visited.add(node);
|
|
372
|
+
recursionStack.add(node);
|
|
373
|
+
const neighbors = resourceGraph.get(node) || new Set();
|
|
374
|
+
for (const neighbor of neighbors) {
|
|
375
|
+
if (hasCycle(neighbor)) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
recursionStack.delete(node);
|
|
380
|
+
return false;
|
|
381
|
+
};
|
|
382
|
+
for (const resource of resourceGraph.keys()) {
|
|
383
|
+
if (hasCycle(resource)) {
|
|
384
|
+
errors.push(`Circular dependency detected involving resource '${resource}'`);
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
errors.push(`Status shape validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
isValid: errors.length === 0,
|
|
395
|
+
errors,
|
|
396
|
+
warnings,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Extract referenced resources from composition context
|
|
401
|
+
*/
|
|
402
|
+
extractReferencedResources(context) {
|
|
403
|
+
if (!context) {
|
|
404
|
+
const currentContext = getCurrentCompositionContext();
|
|
405
|
+
if (!currentContext) {
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
context = currentContext;
|
|
409
|
+
}
|
|
410
|
+
return Object.keys(context.resources);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Extract KubernetesRef objects from a resource
|
|
414
|
+
*/
|
|
415
|
+
extractKubernetesRefsFromResource(resource) {
|
|
416
|
+
const refs = [];
|
|
417
|
+
const traverse = (obj) => {
|
|
418
|
+
if (isKubernetesRef(obj)) {
|
|
419
|
+
refs.push(obj);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (Array.isArray(obj)) {
|
|
423
|
+
obj.forEach(traverse);
|
|
424
|
+
}
|
|
425
|
+
else if (obj && typeof obj === 'object') {
|
|
426
|
+
Object.values(obj).forEach(traverse);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
traverse(resource);
|
|
430
|
+
return refs;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Context-aware resource tracking for composition integration
|
|
435
|
+
*/
|
|
436
|
+
export class CompositionContextTracker {
|
|
437
|
+
contextAnalysisCache = new Map();
|
|
438
|
+
resourceKubernetesRefCache = new Map();
|
|
439
|
+
/**
|
|
440
|
+
* Track KubernetesRef usage in a composition context
|
|
441
|
+
*/
|
|
442
|
+
trackCompositionContext(context) {
|
|
443
|
+
const allKubernetesRefs = [];
|
|
444
|
+
const resourcesWithKubernetesRefs = [];
|
|
445
|
+
const crossResourceReferences = [];
|
|
446
|
+
// Analyze all resources in the context
|
|
447
|
+
for (const [resourceId, resource] of Object.entries(context.resources)) {
|
|
448
|
+
const refs = this.extractKubernetesRefsFromResource(resource);
|
|
449
|
+
if (refs.length > 0) {
|
|
450
|
+
resourcesWithKubernetesRefs.push(resourceId);
|
|
451
|
+
allKubernetesRefs.push(...refs);
|
|
452
|
+
// Cache the refs for this resource
|
|
453
|
+
this.resourceKubernetesRefCache.set(resourceId, refs);
|
|
454
|
+
// Identify cross-resource references
|
|
455
|
+
for (const ref of refs) {
|
|
456
|
+
if (ref.resourceId !== resourceId && ref.resourceId !== '__schema__') {
|
|
457
|
+
crossResourceReferences.push({
|
|
458
|
+
sourceResource: resourceId,
|
|
459
|
+
targetResource: ref.resourceId,
|
|
460
|
+
fieldPath: ref.fieldPath,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
totalKubernetesRefs: allKubernetesRefs.length,
|
|
468
|
+
resourcesWithKubernetesRefs,
|
|
469
|
+
crossResourceReferences,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Get cached KubernetesRef objects for a resource
|
|
474
|
+
*/
|
|
475
|
+
getCachedResourceKubernetesRefs(resourceId) {
|
|
476
|
+
return this.resourceKubernetesRefCache.get(resourceId) || [];
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Clear caches for a specific context
|
|
480
|
+
*/
|
|
481
|
+
clearContextCache(contextId) {
|
|
482
|
+
this.contextAnalysisCache.delete(contextId);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Extract KubernetesRef objects from a resource
|
|
486
|
+
*/
|
|
487
|
+
extractKubernetesRefsFromResource(resource) {
|
|
488
|
+
const refs = [];
|
|
489
|
+
const traverse = (obj, path = '') => {
|
|
490
|
+
if (isKubernetesRef(obj)) {
|
|
491
|
+
refs.push(obj);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (Array.isArray(obj)) {
|
|
495
|
+
obj.forEach((item, index) => traverse(item, `${path}[${index}]`));
|
|
496
|
+
}
|
|
497
|
+
else if (obj && typeof obj === 'object') {
|
|
498
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
499
|
+
const newPath = path ? `${path}.${key}` : key;
|
|
500
|
+
traverse(value, newPath);
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
traverse(resource);
|
|
505
|
+
return refs;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Magic proxy scoping manager for composition contexts with nested composition support
|
|
510
|
+
*/
|
|
511
|
+
export class MagicProxyScopeManager {
|
|
512
|
+
scopeStack = [];
|
|
513
|
+
scopeRegistry = new Map();
|
|
514
|
+
/**
|
|
515
|
+
* Enter a new composition scope
|
|
516
|
+
*/
|
|
517
|
+
enterScope(contextId, schemaProxy) {
|
|
518
|
+
const parentScope = this.getCurrentScope();
|
|
519
|
+
const depth = parentScope ? parentScope.depth + 1 : 0;
|
|
520
|
+
const newScope = {
|
|
521
|
+
contextId,
|
|
522
|
+
resourceIds: new Set(),
|
|
523
|
+
schemaProxy,
|
|
524
|
+
parentScope,
|
|
525
|
+
childScopes: [],
|
|
526
|
+
depth,
|
|
527
|
+
};
|
|
528
|
+
// Link to parent scope
|
|
529
|
+
if (parentScope) {
|
|
530
|
+
parentScope.childScopes.push(newScope);
|
|
531
|
+
}
|
|
532
|
+
this.scopeStack.push(newScope);
|
|
533
|
+
this.scopeRegistry.set(contextId, newScope);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Exit the current composition scope
|
|
537
|
+
*/
|
|
538
|
+
exitScope() {
|
|
539
|
+
const exitingScope = this.scopeStack.pop();
|
|
540
|
+
if (exitingScope) {
|
|
541
|
+
this.scopeRegistry.delete(exitingScope.contextId);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Register a resource in the current scope
|
|
546
|
+
*/
|
|
547
|
+
registerResource(resourceId) {
|
|
548
|
+
const currentScope = this.getCurrentScope();
|
|
549
|
+
if (currentScope) {
|
|
550
|
+
currentScope.resourceIds.add(resourceId);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Register merged resources from a nested composition
|
|
555
|
+
*/
|
|
556
|
+
registerMergedResources(contextId, mergedResourceIds) {
|
|
557
|
+
const scope = this.scopeRegistry.get(contextId);
|
|
558
|
+
if (scope) {
|
|
559
|
+
scope.mergedResourceIds = mergedResourceIds;
|
|
560
|
+
// Also register these resources in the current scope for accessibility
|
|
561
|
+
const currentScope = this.getCurrentScope();
|
|
562
|
+
if (currentScope && currentScope !== scope) {
|
|
563
|
+
mergedResourceIds.forEach(id => currentScope.resourceIds.add(id));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Get the current scope
|
|
569
|
+
*/
|
|
570
|
+
getCurrentScope() {
|
|
571
|
+
return this.scopeStack[this.scopeStack.length - 1];
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Get a scope by context ID
|
|
575
|
+
*/
|
|
576
|
+
getScope(contextId) {
|
|
577
|
+
return this.scopeRegistry.get(contextId);
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Check if a resource is accessible in the current scope (including parent scopes)
|
|
581
|
+
*/
|
|
582
|
+
isResourceAccessible(resourceId) {
|
|
583
|
+
const currentScope = this.getCurrentScope();
|
|
584
|
+
if (!currentScope) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
// Check current scope and all parent scopes
|
|
588
|
+
let scope = currentScope;
|
|
589
|
+
while (scope) {
|
|
590
|
+
if (scope.resourceIds.has(resourceId)) {
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
// Check merged resources from nested compositions
|
|
594
|
+
if (scope.mergedResourceIds?.includes(resourceId)) {
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
scope = scope.parentScope;
|
|
598
|
+
}
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Check if a resource is in the current scope only
|
|
603
|
+
*/
|
|
604
|
+
isResourceInCurrentScope(resourceId) {
|
|
605
|
+
const currentScope = this.getCurrentScope();
|
|
606
|
+
return currentScope ? currentScope.resourceIds.has(resourceId) : false;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Get all accessible resources (current scope + parent scopes)
|
|
610
|
+
*/
|
|
611
|
+
getAccessibleResources() {
|
|
612
|
+
const currentScope = this.getCurrentScope();
|
|
613
|
+
if (!currentScope) {
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
const accessibleResources = new Set();
|
|
617
|
+
// Collect resources from current scope and all parent scopes
|
|
618
|
+
let scope = currentScope;
|
|
619
|
+
while (scope) {
|
|
620
|
+
scope.resourceIds.forEach(id => accessibleResources.add(id));
|
|
621
|
+
// Add merged resources from nested compositions
|
|
622
|
+
if (scope.mergedResourceIds) {
|
|
623
|
+
scope.mergedResourceIds.forEach(id => accessibleResources.add(id));
|
|
624
|
+
}
|
|
625
|
+
scope = scope.parentScope;
|
|
626
|
+
}
|
|
627
|
+
return Array.from(accessibleResources);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Get resources in the current scope only
|
|
631
|
+
*/
|
|
632
|
+
getCurrentScopeResources() {
|
|
633
|
+
const currentScope = this.getCurrentScope();
|
|
634
|
+
return currentScope ? Array.from(currentScope.resourceIds) : [];
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Get the scope hierarchy as a string for debugging
|
|
638
|
+
*/
|
|
639
|
+
getScopeHierarchy() {
|
|
640
|
+
const currentScope = this.getCurrentScope();
|
|
641
|
+
if (!currentScope) {
|
|
642
|
+
return 'No active scope';
|
|
643
|
+
}
|
|
644
|
+
const hierarchy = [];
|
|
645
|
+
let scope = currentScope;
|
|
646
|
+
while (scope) {
|
|
647
|
+
hierarchy.unshift(`${scope.contextId} (depth: ${scope.depth}, resources: ${scope.resourceIds.size})`);
|
|
648
|
+
scope = scope.parentScope;
|
|
649
|
+
}
|
|
650
|
+
return hierarchy.join(' -> ');
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Validate KubernetesRef scope with nested composition support
|
|
654
|
+
*/
|
|
655
|
+
validateKubernetesRefScope(kubernetesRef) {
|
|
656
|
+
const currentScope = this.getCurrentScope();
|
|
657
|
+
if (!currentScope) {
|
|
658
|
+
return { isValid: false, error: 'No active composition scope' };
|
|
659
|
+
}
|
|
660
|
+
// Schema references are always valid
|
|
661
|
+
if (kubernetesRef.resourceId === '__schema__') {
|
|
662
|
+
return { isValid: true };
|
|
663
|
+
}
|
|
664
|
+
// Check accessibility in nested scope hierarchy
|
|
665
|
+
let scope = currentScope;
|
|
666
|
+
while (scope) {
|
|
667
|
+
if (scope.resourceIds.has(kubernetesRef.resourceId)) {
|
|
668
|
+
return {
|
|
669
|
+
isValid: true,
|
|
670
|
+
scopeInfo: {
|
|
671
|
+
foundInScope: scope.contextId,
|
|
672
|
+
scopeDepth: scope.depth,
|
|
673
|
+
},
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
// Check merged resources from nested compositions
|
|
677
|
+
if (scope.mergedResourceIds?.includes(kubernetesRef.resourceId)) {
|
|
678
|
+
return {
|
|
679
|
+
isValid: true,
|
|
680
|
+
scopeInfo: {
|
|
681
|
+
foundInScope: scope.contextId,
|
|
682
|
+
scopeDepth: scope.depth,
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
scope = scope.parentScope;
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
isValid: false,
|
|
690
|
+
error: `Resource '${kubernetesRef.resourceId}' is not accessible in the current composition scope hierarchy`,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Get nested composition statistics
|
|
695
|
+
*/
|
|
696
|
+
getNestedCompositionStats() {
|
|
697
|
+
const currentScope = this.getCurrentScope();
|
|
698
|
+
const stats = {
|
|
699
|
+
totalScopes: this.scopeRegistry.size,
|
|
700
|
+
maxDepth: 0,
|
|
701
|
+
currentDepth: currentScope?.depth || 0,
|
|
702
|
+
totalResources: 0,
|
|
703
|
+
resourcesByScope: {},
|
|
704
|
+
};
|
|
705
|
+
for (const [contextId, scope] of this.scopeRegistry) {
|
|
706
|
+
stats.maxDepth = Math.max(stats.maxDepth, scope.depth);
|
|
707
|
+
stats.totalResources += scope.resourceIds.size;
|
|
708
|
+
stats.resourcesByScope[contextId] = scope.resourceIds.size;
|
|
709
|
+
if (scope.mergedResourceIds) {
|
|
710
|
+
stats.totalResources += scope.mergedResourceIds.length;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return stats;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Integration hooks for kubernetesComposition API
|
|
718
|
+
*/
|
|
719
|
+
export class CompositionIntegrationHooks {
|
|
720
|
+
analyzer;
|
|
721
|
+
contextTracker;
|
|
722
|
+
scopeManager;
|
|
723
|
+
constructor() {
|
|
724
|
+
this.analyzer = new CompositionExpressionAnalyzer();
|
|
725
|
+
this.contextTracker = new CompositionContextTracker();
|
|
726
|
+
this.scopeManager = new MagicProxyScopeManager();
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Hook called before composition execution to set up expression analysis
|
|
730
|
+
*/
|
|
731
|
+
beforeCompositionExecution(compositionFn, schemaProxy, contextId) {
|
|
732
|
+
// Enter the composition scope
|
|
733
|
+
this.scopeManager.enterScope(contextId, schemaProxy);
|
|
734
|
+
// Pre-analyze the composition for optimization opportunities
|
|
735
|
+
try {
|
|
736
|
+
const analysisResult = this.analyzer.analyzeCompositionFunction(compositionFn, schemaProxy);
|
|
737
|
+
// Store analysis result for later use during serialization
|
|
738
|
+
if (analysisResult.requiresCelConversion) {
|
|
739
|
+
console.debug(`Composition ${contextId} requires CEL conversion: ${analysisResult.conversionMetadata.kubernetesRefsDetected} KubernetesRef objects detected`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
// Non-fatal error - composition can continue without pre-analysis
|
|
744
|
+
console.warn(`Composition pre-analysis failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Hook called after composition execution to process results
|
|
749
|
+
*/
|
|
750
|
+
afterCompositionExecution(statusShape, factoryType = 'kro', context) {
|
|
751
|
+
try {
|
|
752
|
+
// Track the composition context if provided
|
|
753
|
+
if (context) {
|
|
754
|
+
const trackingResult = this.contextTracker.trackCompositionContext(context);
|
|
755
|
+
if (trackingResult.crossResourceReferences.length > 0) {
|
|
756
|
+
console.debug(`Composition has ${trackingResult.crossResourceReferences.length} cross-resource references that may require CEL conversion`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// Process the status shape
|
|
760
|
+
const processedStatus = this.analyzer.processCompositionStatus(statusShape, factoryType);
|
|
761
|
+
return processedStatus;
|
|
762
|
+
}
|
|
763
|
+
finally {
|
|
764
|
+
// Exit the composition scope
|
|
765
|
+
this.scopeManager.exitScope();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Hook called during resource creation to analyze KubernetesRef usage
|
|
770
|
+
*/
|
|
771
|
+
onResourceCreation(resourceId, resource, _context) {
|
|
772
|
+
// Register the resource in the current scope
|
|
773
|
+
this.scopeManager.registerResource(resourceId);
|
|
774
|
+
// Analyze KubernetesRef usage
|
|
775
|
+
const refs = this.contextTracker.getCachedResourceKubernetesRefs(resourceId) ||
|
|
776
|
+
this.contextTracker.extractKubernetesRefsFromResource(resource);
|
|
777
|
+
if (refs.length > 0) {
|
|
778
|
+
console.debug(`Resource ${resourceId} contains ${refs.length} KubernetesRef objects that may require CEL conversion`);
|
|
779
|
+
// Validate scope for each KubernetesRef
|
|
780
|
+
for (const ref of refs) {
|
|
781
|
+
const validation = this.scopeManager.validateKubernetesRefScope(ref);
|
|
782
|
+
if (!validation.isValid) {
|
|
783
|
+
console.warn(`KubernetesRef validation failed for resource ${resourceId}: ${validation.error}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Get the current magic proxy scope manager
|
|
790
|
+
*/
|
|
791
|
+
getScopeManager() {
|
|
792
|
+
return this.scopeManager;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Get the context tracker
|
|
796
|
+
*/
|
|
797
|
+
getContextTracker() {
|
|
798
|
+
return this.contextTracker;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Handle auto-registration of resources with KubernetesRef tracking
|
|
802
|
+
*/
|
|
803
|
+
handleAutoRegistration(resourceId, resource, context) {
|
|
804
|
+
// Register the resource in the current scope
|
|
805
|
+
this.scopeManager.registerResource(resourceId);
|
|
806
|
+
// Track KubernetesRef objects in the auto-registered resource
|
|
807
|
+
const refs = this.contextTracker.extractKubernetesRefsFromResource(resource);
|
|
808
|
+
if (refs.length > 0) {
|
|
809
|
+
// Validate that all referenced resources are accessible
|
|
810
|
+
for (const ref of refs) {
|
|
811
|
+
const validation = this.scopeManager.validateKubernetesRefScope(ref);
|
|
812
|
+
if (!validation.isValid) {
|
|
813
|
+
console.warn(`Auto-registered resource '${resourceId}' contains invalid KubernetesRef: ${validation.error}`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// Cache the KubernetesRef objects for later use
|
|
817
|
+
this.contextTracker.resourceKubernetesRefCache.set(resourceId, refs);
|
|
818
|
+
}
|
|
819
|
+
// Update the composition context
|
|
820
|
+
context.addResource(resourceId, resource);
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Handle side-effect based resource creation with magic proxy integration
|
|
824
|
+
*/
|
|
825
|
+
handleSideEffectCreation(resourceFactory, resourceId, context) {
|
|
826
|
+
const activeContext = context || getCurrentCompositionContext();
|
|
827
|
+
if (!activeContext) {
|
|
828
|
+
// No active composition context - create resource normally
|
|
829
|
+
return resourceFactory();
|
|
830
|
+
}
|
|
831
|
+
// Track resources before creation
|
|
832
|
+
const resourcesBefore = new Set(Object.keys(activeContext.resources));
|
|
833
|
+
// Create the resource
|
|
834
|
+
const resource = resourceFactory();
|
|
835
|
+
// Determine the actual resource ID
|
|
836
|
+
const actualResourceId = resourceId || this.generateResourceIdFromResource(resource);
|
|
837
|
+
// Check if new resources were created as side effects
|
|
838
|
+
const resourcesAfter = Object.keys(activeContext.resources);
|
|
839
|
+
const newResources = resourcesAfter.filter(id => !resourcesBefore.has(id));
|
|
840
|
+
// Handle each new resource
|
|
841
|
+
for (const newResourceId of newResources) {
|
|
842
|
+
const newResource = activeContext.resources[newResourceId];
|
|
843
|
+
if (newResource) {
|
|
844
|
+
this.handleAutoRegistration(newResourceId, newResource, activeContext);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
// Handle the main resource if it wasn't already registered
|
|
848
|
+
if (!activeContext.resources[actualResourceId]) {
|
|
849
|
+
this.handleAutoRegistration(actualResourceId, resource, activeContext);
|
|
850
|
+
}
|
|
851
|
+
return resource;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Track magic proxy usage during composition execution
|
|
855
|
+
*/
|
|
856
|
+
trackMagicProxyUsage(proxyAccess) {
|
|
857
|
+
const currentScope = this.scopeManager.getCurrentScope();
|
|
858
|
+
if (!currentScope) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
// Validate that the accessed resource is in scope
|
|
862
|
+
if (proxyAccess.resourceId !== '__schema__' &&
|
|
863
|
+
!this.scopeManager.isResourceAccessible(proxyAccess.resourceId)) {
|
|
864
|
+
console.warn(`Magic proxy access to out-of-scope resource: ${proxyAccess.resourceId}.${proxyAccess.fieldPath}`);
|
|
865
|
+
}
|
|
866
|
+
// Track KubernetesRef creation from magic proxy access
|
|
867
|
+
if (proxyAccess.accessType === 'read' && isKubernetesRef(proxyAccess.value)) {
|
|
868
|
+
const validation = this.scopeManager.validateKubernetesRefScope(proxyAccess.value);
|
|
869
|
+
if (!validation.isValid) {
|
|
870
|
+
console.warn(`Magic proxy created invalid KubernetesRef: ${validation.error}`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Ensure compatibility with existing factory functions
|
|
876
|
+
*/
|
|
877
|
+
ensureFactoryCompatibility(factoryFn, factoryName) {
|
|
878
|
+
const currentScope = this.scopeManager.getCurrentScope();
|
|
879
|
+
if (!currentScope) {
|
|
880
|
+
// No active composition - use factory normally
|
|
881
|
+
return factoryFn();
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
884
|
+
// Execute factory with side-effect tracking
|
|
885
|
+
return this.handleSideEffectCreation(factoryFn, undefined, getCurrentCompositionContext());
|
|
886
|
+
}
|
|
887
|
+
catch (error) {
|
|
888
|
+
console.error(`Factory function '${factoryName}' failed in composition context: ${error instanceof Error ? error.message : String(error)}`);
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Generate a resource ID from a resource object
|
|
894
|
+
*/
|
|
895
|
+
generateResourceIdFromResource(resource) {
|
|
896
|
+
// Try to extract ID from common resource properties
|
|
897
|
+
const resourceObj = resource;
|
|
898
|
+
if (resourceObj.__resourceId) {
|
|
899
|
+
return resourceObj.__resourceId;
|
|
900
|
+
}
|
|
901
|
+
if (resourceObj.metadata?.name) {
|
|
902
|
+
const kind = resourceObj.kind || 'resource';
|
|
903
|
+
return `${kind.toLowerCase()}-${resourceObj.metadata.name}`;
|
|
904
|
+
}
|
|
905
|
+
if (resourceObj.kind) {
|
|
906
|
+
return `${resourceObj.kind.toLowerCase()}-${Date.now()}`;
|
|
907
|
+
}
|
|
908
|
+
return `resource-${Date.now()}`;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Global composition integration instance
|
|
913
|
+
*/
|
|
914
|
+
export const compositionIntegration = new CompositionIntegrationHooks();
|
|
915
|
+
/**
|
|
916
|
+
* Utility function to check if a composition function uses KubernetesRef objects
|
|
917
|
+
*/
|
|
918
|
+
export function compositionUsesKubernetesRefs(compositionFn, schemaProxy) {
|
|
919
|
+
const analyzer = new CompositionExpressionAnalyzer();
|
|
920
|
+
try {
|
|
921
|
+
const result = analyzer.analyzeCompositionFunction(compositionFn, schemaProxy);
|
|
922
|
+
return result.requiresCelConversion;
|
|
923
|
+
}
|
|
924
|
+
catch {
|
|
925
|
+
// If analysis fails, assume it might use KubernetesRef objects to be safe
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Utility function to get composition analysis metadata
|
|
931
|
+
*/
|
|
932
|
+
export function getCompositionAnalysis(compositionFn, schemaProxy) {
|
|
933
|
+
const analyzer = new CompositionExpressionAnalyzer();
|
|
934
|
+
return analyzer.analyzeCompositionFunction(compositionFn, schemaProxy);
|
|
935
|
+
}
|
|
936
|
+
//# sourceMappingURL=composition-integration.js.map
|