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,1306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Type Optionality Handler for JavaScript to CEL Expression Conversion
|
|
3
|
+
*
|
|
4
|
+
* This module handles the mismatch between Enhanced type compile-time non-optionality
|
|
5
|
+
* and runtime optionality during field hydration. Enhanced types show fields as
|
|
6
|
+
* non-optional at compile time, but KubernetesRef objects might resolve to undefined
|
|
7
|
+
* during field hydration.
|
|
8
|
+
*
|
|
9
|
+
* Key Features:
|
|
10
|
+
* - Automatic null-safety detection for Enhanced type KubernetesRef objects
|
|
11
|
+
* - CEL expression generation with has() checks for potentially undefined fields
|
|
12
|
+
* - Support for optional chaining with Enhanced types that appear non-optional
|
|
13
|
+
* - Integration with field hydration timing to handle undefined-to-defined transitions
|
|
14
|
+
* - Context-aware optionality handling based on field hydration state
|
|
15
|
+
*/
|
|
16
|
+
import { ConversionError } from '../errors.js';
|
|
17
|
+
import { getComponentLogger } from '../logging/index.js';
|
|
18
|
+
import { isKubernetesRef, } from '../../utils/type-guards.js';
|
|
19
|
+
import { CEL_EXPRESSION_BRAND, KUBERNETES_REF_BRAND } from '../constants/brands.js';
|
|
20
|
+
/**
|
|
21
|
+
* Default optionality handling options
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_OPTIONALITY_OPTIONS = {
|
|
24
|
+
deepAnalysis: true,
|
|
25
|
+
conservative: true,
|
|
26
|
+
useKroConditionals: true,
|
|
27
|
+
generateHasChecks: true,
|
|
28
|
+
maxDepth: 5,
|
|
29
|
+
includeReasoning: true
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Enhanced Type Optionality Handler
|
|
33
|
+
*
|
|
34
|
+
* Handles the complexity of Enhanced types that appear non-optional at compile time
|
|
35
|
+
* but may be undefined at runtime during field hydration.
|
|
36
|
+
*/
|
|
37
|
+
export class EnhancedTypeOptionalityHandler {
|
|
38
|
+
options;
|
|
39
|
+
logger = getComponentLogger('optionality-handler');
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.options = { ...DEFAULT_OPTIONALITY_OPTIONS, ...options };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Analyze KubernetesRef objects for optionality requirements
|
|
45
|
+
*
|
|
46
|
+
* This method determines whether KubernetesRef objects in expressions require
|
|
47
|
+
* null-safety handling based on Enhanced type behavior and field hydration timing.
|
|
48
|
+
*/
|
|
49
|
+
analyzeOptionalityRequirements(expression, context) {
|
|
50
|
+
const results = [];
|
|
51
|
+
try {
|
|
52
|
+
// Extract all KubernetesRef objects from the expression
|
|
53
|
+
const kubernetesRefs = this.extractKubernetesRefs(expression);
|
|
54
|
+
this.logger.debug('Analyzing optionality requirements', {
|
|
55
|
+
expressionType: typeof expression,
|
|
56
|
+
kubernetesRefCount: kubernetesRefs.length,
|
|
57
|
+
contextType: context.type
|
|
58
|
+
});
|
|
59
|
+
for (const ref of kubernetesRefs) {
|
|
60
|
+
const analysis = this.analyzeKubernetesRefOptionality(ref, context);
|
|
61
|
+
results.push(analysis);
|
|
62
|
+
}
|
|
63
|
+
return results;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
this.logger.error('Failed to analyze optionality requirements', error);
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Generate CEL expressions with appropriate null-safety checks
|
|
72
|
+
*
|
|
73
|
+
* This method takes the optionality analysis results and generates CEL expressions
|
|
74
|
+
* that include proper null-safety handling for potentially undefined fields.
|
|
75
|
+
*/
|
|
76
|
+
generateNullSafeCelExpression(originalExpression, optionalityResults, context) {
|
|
77
|
+
try {
|
|
78
|
+
// Determine if any KubernetesRef objects require null-safety
|
|
79
|
+
const requiresNullSafety = optionalityResults.some(result => result.requiresNullSafety);
|
|
80
|
+
if (!requiresNullSafety) {
|
|
81
|
+
// No null-safety required, return as-is
|
|
82
|
+
return {
|
|
83
|
+
valid: true,
|
|
84
|
+
celExpression: this.convertToBasicCel(originalExpression, context),
|
|
85
|
+
dependencies: optionalityResults.map(r => r.kubernetesRef),
|
|
86
|
+
sourceMap: [],
|
|
87
|
+
errors: [],
|
|
88
|
+
warnings: [],
|
|
89
|
+
requiresConversion: optionalityResults.length > 0
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Generate null-safe CEL expression
|
|
93
|
+
const nullSafeCel = this.generateNullSafeExpression(originalExpression, optionalityResults, context);
|
|
94
|
+
return {
|
|
95
|
+
valid: true,
|
|
96
|
+
celExpression: nullSafeCel,
|
|
97
|
+
dependencies: optionalityResults.map(r => r.kubernetesRef),
|
|
98
|
+
sourceMap: this.generateSourceMapping(originalExpression, nullSafeCel, context),
|
|
99
|
+
errors: [],
|
|
100
|
+
warnings: [],
|
|
101
|
+
requiresConversion: true
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
const conversionError = new ConversionError(`Failed to generate null-safe CEL expression: ${error instanceof Error ? error.message : String(error)}`, String(originalExpression), 'unknown');
|
|
106
|
+
return {
|
|
107
|
+
valid: false,
|
|
108
|
+
celExpression: null,
|
|
109
|
+
dependencies: optionalityResults.map(r => r.kubernetesRef),
|
|
110
|
+
sourceMap: [],
|
|
111
|
+
errors: [conversionError],
|
|
112
|
+
warnings: [],
|
|
113
|
+
requiresConversion: true
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Handle optional chaining with Enhanced types
|
|
119
|
+
*
|
|
120
|
+
* This method specifically handles cases where optional chaining is used with
|
|
121
|
+
* Enhanced types that appear non-optional at compile time.
|
|
122
|
+
*/
|
|
123
|
+
handleOptionalChainingWithEnhancedTypes(expression, context) {
|
|
124
|
+
try {
|
|
125
|
+
// Detect optional chaining patterns in the expression
|
|
126
|
+
const optionalChainingAnalysis = this.analyzeOptionalChainingPatterns(expression, context);
|
|
127
|
+
if (optionalChainingAnalysis.patterns.length === 0) {
|
|
128
|
+
// No optional chaining detected - analyze for regular optionality
|
|
129
|
+
const optionalityResults = this.analyzeOptionalityRequirements(expression, context);
|
|
130
|
+
return this.generateNullSafeCelExpression(expression, optionalityResults, context);
|
|
131
|
+
}
|
|
132
|
+
// Generate appropriate CEL expressions for optional chaining with Enhanced types
|
|
133
|
+
const celResult = this.generateOptionalChainingCelExpression(expression, optionalChainingAnalysis, context);
|
|
134
|
+
return celResult;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const conversionError = new ConversionError(`Failed to handle optional chaining: ${error instanceof Error ? error.message : String(error)}`, String(expression), 'optional-chaining');
|
|
138
|
+
return {
|
|
139
|
+
valid: false,
|
|
140
|
+
celExpression: null,
|
|
141
|
+
dependencies: this.extractKubernetesRefs(expression),
|
|
142
|
+
sourceMap: [],
|
|
143
|
+
errors: [conversionError],
|
|
144
|
+
warnings: [],
|
|
145
|
+
requiresConversion: true
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Analyze optional chaining patterns in expressions with Enhanced types
|
|
151
|
+
*/
|
|
152
|
+
analyzeOptionalChainingPatterns(expression, context) {
|
|
153
|
+
const patterns = [];
|
|
154
|
+
const enhancedTypeFields = [];
|
|
155
|
+
// Extract KubernetesRef objects that might be involved in optional chaining
|
|
156
|
+
const kubernetesRefs = this.extractKubernetesRefs(expression);
|
|
157
|
+
for (const ref of kubernetesRefs) {
|
|
158
|
+
// Check if this KubernetesRef represents an Enhanced type field
|
|
159
|
+
const enhancedFieldInfo = this.analyzeEnhancedTypeField(ref, context);
|
|
160
|
+
if (enhancedFieldInfo.isEnhancedType) {
|
|
161
|
+
enhancedTypeFields.push(enhancedFieldInfo);
|
|
162
|
+
// Create optional chaining pattern for this Enhanced type field
|
|
163
|
+
const pattern = {
|
|
164
|
+
kubernetesRef: ref,
|
|
165
|
+
fieldPath: ref.fieldPath || '',
|
|
166
|
+
isEnhancedType: true,
|
|
167
|
+
appearsNonOptional: enhancedFieldInfo.appearsNonOptional,
|
|
168
|
+
actuallyOptional: enhancedFieldInfo.actuallyOptional,
|
|
169
|
+
chainingDepth: this.calculateChainingDepth(ref.fieldPath || ''),
|
|
170
|
+
suggestedCelPattern: this.generateOptionalChainingCelPattern(ref, context)
|
|
171
|
+
};
|
|
172
|
+
patterns.push(pattern);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const requiresSpecialHandling = enhancedTypeFields.some(field => field.appearsNonOptional && field.actuallyOptional);
|
|
176
|
+
return { patterns, enhancedTypeFields, requiresSpecialHandling };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Analyze Enhanced type field information
|
|
180
|
+
*/
|
|
181
|
+
analyzeEnhancedTypeField(kubernetesRef, context) {
|
|
182
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
183
|
+
const isStatusField = fieldPath.startsWith('status.');
|
|
184
|
+
// Enhanced types in status fields appear non-optional but are actually optional
|
|
185
|
+
const appearsNonOptional = !fieldPath.includes('?') && !fieldPath.includes('|');
|
|
186
|
+
const actuallyOptional = isStatusField || this.isPotentiallyUndefinedAtRuntime(kubernetesRef, context);
|
|
187
|
+
return {
|
|
188
|
+
kubernetesRef,
|
|
189
|
+
fieldPath,
|
|
190
|
+
isEnhancedType: true,
|
|
191
|
+
appearsNonOptional,
|
|
192
|
+
actuallyOptional,
|
|
193
|
+
isStatusField,
|
|
194
|
+
requiresOptionalChaining: appearsNonOptional && actuallyOptional,
|
|
195
|
+
confidence: this.calculateOptionalityConfidence(kubernetesRef, context)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Generate CEL expression for optional chaining with Enhanced types
|
|
200
|
+
*/
|
|
201
|
+
generateOptionalChainingCelExpression(expression, optionalChainingAnalysis, context) {
|
|
202
|
+
try {
|
|
203
|
+
if (!optionalChainingAnalysis.requiresSpecialHandling) {
|
|
204
|
+
// No special handling needed - use regular conversion
|
|
205
|
+
const optionalityResults = this.analyzeOptionalityRequirements(expression, context);
|
|
206
|
+
return this.generateNullSafeCelExpression(expression, optionalityResults, context);
|
|
207
|
+
}
|
|
208
|
+
// Generate CEL expression with proper optional chaining support
|
|
209
|
+
let celExpression;
|
|
210
|
+
if (context.useKroConditionals) {
|
|
211
|
+
// Use Kro's conditional operators for optional chaining
|
|
212
|
+
celExpression = this.generateKroOptionalChainingExpression(optionalChainingAnalysis.patterns, context);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
// Use has() checks for optional chaining
|
|
216
|
+
celExpression = this.generateHasCheckOptionalChainingExpression(optionalChainingAnalysis.patterns, context);
|
|
217
|
+
}
|
|
218
|
+
const dependencies = optionalChainingAnalysis.patterns.map(p => p.kubernetesRef);
|
|
219
|
+
return {
|
|
220
|
+
valid: true,
|
|
221
|
+
celExpression: {
|
|
222
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
223
|
+
expression: celExpression,
|
|
224
|
+
type: this.inferExpressionType(expression, context)
|
|
225
|
+
},
|
|
226
|
+
dependencies,
|
|
227
|
+
sourceMap: this.generateSourceMapping(expression, { expression: celExpression }, context),
|
|
228
|
+
errors: [],
|
|
229
|
+
warnings: [],
|
|
230
|
+
requiresConversion: true
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
const conversionError = new ConversionError(`Failed to generate optional chaining CEL: ${error instanceof Error ? error.message : String(error)}`, String(expression), 'optional-chaining');
|
|
235
|
+
return {
|
|
236
|
+
valid: false,
|
|
237
|
+
celExpression: null,
|
|
238
|
+
dependencies: optionalChainingAnalysis.patterns.map(p => p.kubernetesRef),
|
|
239
|
+
sourceMap: [],
|
|
240
|
+
errors: [conversionError],
|
|
241
|
+
warnings: [],
|
|
242
|
+
requiresConversion: true
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Generate Kro CEL expression with ? prefix operator for optional chaining
|
|
248
|
+
*
|
|
249
|
+
* Kro uses the ? operator as a prefix before field names for optional access
|
|
250
|
+
*/
|
|
251
|
+
generateKroOptionalChainingExpression(patterns, _context) {
|
|
252
|
+
if (patterns.length === 0) {
|
|
253
|
+
return 'null';
|
|
254
|
+
}
|
|
255
|
+
// For Kro, use ? prefix operator for optional field access
|
|
256
|
+
const expressions = patterns.map(pattern => {
|
|
257
|
+
const resourcePath = pattern.kubernetesRef.resourceId === '__schema__'
|
|
258
|
+
? `schema.${pattern.fieldPath}`
|
|
259
|
+
: `resources.${pattern.kubernetesRef.resourceId}.${pattern.fieldPath}`;
|
|
260
|
+
// Convert field.path.to.value to field.?path.?to.?value (Kro ? prefix syntax)
|
|
261
|
+
const optionalPath = this.convertToKroOptionalSyntax(resourcePath);
|
|
262
|
+
return optionalPath;
|
|
263
|
+
});
|
|
264
|
+
// Combine multiple patterns if needed
|
|
265
|
+
if (expressions.length === 1) {
|
|
266
|
+
return expressions[0] || 'null';
|
|
267
|
+
}
|
|
268
|
+
// For multiple patterns, use logical AND
|
|
269
|
+
return expressions.join(' && ');
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Convert a field path to Kro's ? prefix optional syntax
|
|
273
|
+
* Example: resources.service.status.loadBalancer.ingress[0].ip
|
|
274
|
+
* Becomes: resources.service.status.?loadBalancer.?ingress[0].?ip
|
|
275
|
+
*
|
|
276
|
+
* The ? operator should be placed before fields that might not exist
|
|
277
|
+
*/
|
|
278
|
+
convertToKroOptionalSyntax(resourcePath) {
|
|
279
|
+
// Split the path into parts, handling array access
|
|
280
|
+
const parts = resourcePath.split('.');
|
|
281
|
+
const result = [];
|
|
282
|
+
for (let i = 0; i < parts.length; i++) {
|
|
283
|
+
const part = parts[i];
|
|
284
|
+
// Ensure part is defined
|
|
285
|
+
if (!part)
|
|
286
|
+
continue;
|
|
287
|
+
// Don't add ? to root parts (resources, schema) or the resource ID
|
|
288
|
+
if (i < 3) {
|
|
289
|
+
result.push(part);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
// Add ? prefix for optional access to nested fields that might not exist
|
|
293
|
+
if (part.includes('[')) {
|
|
294
|
+
// Handle array access: field[0] becomes ?field[0]
|
|
295
|
+
result.push(`?${part}`);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
result.push(`?${part}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return result.join('.');
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Generate has() check expression for optional chaining
|
|
306
|
+
*/
|
|
307
|
+
generateHasCheckOptionalChainingExpression(patterns, _context) {
|
|
308
|
+
if (patterns.length === 0) {
|
|
309
|
+
return 'null';
|
|
310
|
+
}
|
|
311
|
+
const expressions = [];
|
|
312
|
+
for (const pattern of patterns) {
|
|
313
|
+
const resourcePath = pattern.kubernetesRef.resourceId === '__schema__'
|
|
314
|
+
? `schema.${pattern.fieldPath}`
|
|
315
|
+
: `resources.${pattern.kubernetesRef.resourceId}.${pattern.fieldPath}`;
|
|
316
|
+
// Generate nested has() checks for the field path
|
|
317
|
+
const hasChecks = this.generateNestedHasChecksForPath(resourcePath);
|
|
318
|
+
const finalExpression = `${hasChecks.join(' && ')} && ${resourcePath}`;
|
|
319
|
+
expressions.push(finalExpression);
|
|
320
|
+
}
|
|
321
|
+
return expressions.join(' && ');
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Generate nested has() checks for a field path
|
|
325
|
+
*/
|
|
326
|
+
generateNestedHasChecksForPath(resourcePath) {
|
|
327
|
+
const checks = [];
|
|
328
|
+
const parts = resourcePath.split('.');
|
|
329
|
+
for (let i = 0; i < parts.length; i++) {
|
|
330
|
+
const partialPath = parts.slice(0, i + 1).join('.');
|
|
331
|
+
checks.push(`has(${partialPath})`);
|
|
332
|
+
}
|
|
333
|
+
return checks;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Calculate chaining depth for a field path
|
|
337
|
+
*/
|
|
338
|
+
calculateChainingDepth(fieldPath) {
|
|
339
|
+
return fieldPath.split('.').length;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Generate optional chaining CEL pattern for a KubernetesRef
|
|
343
|
+
*/
|
|
344
|
+
generateOptionalChainingCelPattern(kubernetesRef, context) {
|
|
345
|
+
const resourcePath = kubernetesRef.resourceId === '__schema__'
|
|
346
|
+
? `schema.${kubernetesRef.fieldPath}`
|
|
347
|
+
: `resources.${kubernetesRef.resourceId}.${kubernetesRef.fieldPath}`;
|
|
348
|
+
if (context.useKroConditionals) {
|
|
349
|
+
// Use Kro's ? prefix operator for optional access
|
|
350
|
+
return this.convertToKroOptionalSyntax(resourcePath);
|
|
351
|
+
}
|
|
352
|
+
// Fallback to has() checks for better null safety
|
|
353
|
+
const hasChecks = this.generateNestedHasChecksForPath(resourcePath);
|
|
354
|
+
return `${hasChecks.join(' && ')} ? ${resourcePath} : null`;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Automatically detect null-safety requirements for Enhanced type KubernetesRef objects
|
|
358
|
+
*
|
|
359
|
+
* This method analyzes Enhanced types and their KubernetesRef objects to determine
|
|
360
|
+
* which fields require null-safety checks despite appearing non-optional at compile time.
|
|
361
|
+
*/
|
|
362
|
+
detectNullSafetyRequirements(enhancedResources, context) {
|
|
363
|
+
const nullSafetyMap = new Map();
|
|
364
|
+
try {
|
|
365
|
+
this.logger.debug('Detecting null-safety requirements for Enhanced types', {
|
|
366
|
+
resourceCount: Object.keys(enhancedResources).length,
|
|
367
|
+
contextType: context.type
|
|
368
|
+
});
|
|
369
|
+
for (const [resourceId, enhancedResource] of Object.entries(enhancedResources)) {
|
|
370
|
+
const resourceAnalysis = [];
|
|
371
|
+
// Analyze the Enhanced resource for potential KubernetesRef objects
|
|
372
|
+
const potentialRefs = this.extractPotentialKubernetesRefsFromEnhanced(enhancedResource, resourceId);
|
|
373
|
+
for (const ref of potentialRefs) {
|
|
374
|
+
const analysis = this.analyzeKubernetesRefOptionality(ref, context);
|
|
375
|
+
// Enhanced types require special handling
|
|
376
|
+
if (analysis.potentiallyUndefined) {
|
|
377
|
+
analysis.reason = `Enhanced type field '${analysis.fieldPath}' appears non-optional at compile time but may be undefined at runtime during field hydration`;
|
|
378
|
+
analysis.requiresNullSafety = true;
|
|
379
|
+
analysis.suggestedCelPattern = this.generateEnhancedTypeNullSafetyPattern(ref, context);
|
|
380
|
+
}
|
|
381
|
+
resourceAnalysis.push(analysis);
|
|
382
|
+
}
|
|
383
|
+
if (resourceAnalysis.length > 0) {
|
|
384
|
+
nullSafetyMap.set(resourceId, resourceAnalysis);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
this.logger.debug('Null-safety detection complete', {
|
|
388
|
+
resourcesWithNullSafety: nullSafetyMap.size,
|
|
389
|
+
totalAnalysisResults: Array.from(nullSafetyMap.values()).reduce((sum, arr) => sum + arr.length, 0)
|
|
390
|
+
});
|
|
391
|
+
return nullSafetyMap;
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
this.logger.error('Failed to detect null-safety requirements', error);
|
|
395
|
+
return new Map();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Generate Enhanced type-specific null-safety patterns
|
|
400
|
+
*/
|
|
401
|
+
generateEnhancedTypeNullSafetyPattern(kubernetesRef, context) {
|
|
402
|
+
const resourcePath = kubernetesRef.resourceId === '__schema__'
|
|
403
|
+
? `schema.${kubernetesRef.fieldPath}`
|
|
404
|
+
: `resources.${kubernetesRef.resourceId}.${kubernetesRef.fieldPath}`;
|
|
405
|
+
// For Enhanced types, we need to be extra careful about null-safety
|
|
406
|
+
if (context.generateHasChecks) {
|
|
407
|
+
// Use has() checks for potentially undefined Enhanced type fields
|
|
408
|
+
if (kubernetesRef.fieldPath?.includes('.')) {
|
|
409
|
+
// For nested fields, check each level
|
|
410
|
+
const pathParts = kubernetesRef.fieldPath.split('.');
|
|
411
|
+
const checks = [];
|
|
412
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
413
|
+
const partialPath = pathParts.slice(0, i + 1).join('.');
|
|
414
|
+
const fullPath = kubernetesRef.resourceId === '__schema__'
|
|
415
|
+
? `schema.${partialPath}`
|
|
416
|
+
: `resources.${kubernetesRef.resourceId}.${partialPath}`;
|
|
417
|
+
checks.push(`has(${fullPath})`);
|
|
418
|
+
}
|
|
419
|
+
return `${checks.join(' && ')} && ${resourcePath}`;
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
return `has(${resourcePath}) && ${resourcePath}`;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (context.useKroConditionals) {
|
|
426
|
+
// Use Kro's ? prefix operator for Enhanced types
|
|
427
|
+
return this.convertToKroOptionalSyntax(resourcePath);
|
|
428
|
+
}
|
|
429
|
+
// Fallback to basic null check
|
|
430
|
+
return `${resourcePath} != null && ${resourcePath}`;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Extract potential KubernetesRef objects from Enhanced resources
|
|
434
|
+
*/
|
|
435
|
+
extractPotentialKubernetesRefsFromEnhanced(_enhancedResource, resourceId) {
|
|
436
|
+
const refs = [];
|
|
437
|
+
// Common field paths that might contain KubernetesRef objects in Enhanced types
|
|
438
|
+
const commonFieldPaths = [
|
|
439
|
+
'status.readyReplicas',
|
|
440
|
+
'status.availableReplicas',
|
|
441
|
+
'status.conditions',
|
|
442
|
+
'status.phase',
|
|
443
|
+
'status.podIP',
|
|
444
|
+
'status.hostIP',
|
|
445
|
+
'status.loadBalancer.ingress',
|
|
446
|
+
'spec.replicas',
|
|
447
|
+
'spec.selector',
|
|
448
|
+
'metadata.name',
|
|
449
|
+
'metadata.namespace',
|
|
450
|
+
'metadata.labels',
|
|
451
|
+
'metadata.annotations'
|
|
452
|
+
];
|
|
453
|
+
for (const fieldPath of commonFieldPaths) {
|
|
454
|
+
// Create a potential KubernetesRef for analysis
|
|
455
|
+
const potentialRef = {
|
|
456
|
+
[KUBERNETES_REF_BRAND]: true,
|
|
457
|
+
resourceId,
|
|
458
|
+
fieldPath,
|
|
459
|
+
type: 'unknown'
|
|
460
|
+
};
|
|
461
|
+
refs.push(potentialRef);
|
|
462
|
+
}
|
|
463
|
+
return refs;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Integrate with field hydration timing
|
|
467
|
+
*
|
|
468
|
+
* This method provides integration with TypeKro's field hydration system to
|
|
469
|
+
* handle the transition from undefined to defined values during hydration.
|
|
470
|
+
*/
|
|
471
|
+
integrateWithFieldHydrationTiming(expression, hydrationStates, context) {
|
|
472
|
+
try {
|
|
473
|
+
const kubernetesRefs = this.extractKubernetesRefs(expression);
|
|
474
|
+
// Analyze hydration states for all references
|
|
475
|
+
const hydrationAnalysis = this.analyzeHydrationStates(kubernetesRefs, hydrationStates);
|
|
476
|
+
// Generate expressions for different hydration phases
|
|
477
|
+
const preHydrationExpression = this.generatePreHydrationExpression(expression, hydrationAnalysis.unhydratedRefs, context);
|
|
478
|
+
const postHydrationExpression = this.generatePostHydrationExpression(expression, hydrationAnalysis.hydratedRefs, context);
|
|
479
|
+
const hydrationDependentExpression = this.generateHydrationDependentExpression(expression, hydrationAnalysis.hydratingRefs, context);
|
|
480
|
+
// Generate transition handlers for undefined-to-defined transitions
|
|
481
|
+
const transitionHandlers = this.generateHydrationTransitionHandlers(expression, hydrationAnalysis, context);
|
|
482
|
+
return {
|
|
483
|
+
preHydrationExpression,
|
|
484
|
+
postHydrationExpression,
|
|
485
|
+
hydrationDependentExpression,
|
|
486
|
+
transitionHandlers
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
this.logger.error('Failed to integrate with field hydration timing', error);
|
|
491
|
+
return {
|
|
492
|
+
preHydrationExpression: null,
|
|
493
|
+
postHydrationExpression: null,
|
|
494
|
+
hydrationDependentExpression: null,
|
|
495
|
+
transitionHandlers: []
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Handle undefined-to-defined transitions during field hydration
|
|
501
|
+
*
|
|
502
|
+
* This method creates handlers for the transition from undefined to defined
|
|
503
|
+
* values as fields are hydrated over time.
|
|
504
|
+
*/
|
|
505
|
+
handleUndefinedToDefinedTransitions(expression, hydrationStates, context) {
|
|
506
|
+
try {
|
|
507
|
+
const kubernetesRefs = this.extractKubernetesRefs(expression);
|
|
508
|
+
const transitionPlan = this.createTransitionPlan(kubernetesRefs, hydrationStates, context);
|
|
509
|
+
return {
|
|
510
|
+
transitionPlan,
|
|
511
|
+
phaseExpressions: this.generatePhaseExpressions(expression, transitionPlan, context),
|
|
512
|
+
watchExpressions: this.generateWatchExpressions(transitionPlan, context),
|
|
513
|
+
fallbackExpressions: this.generateFallbackExpressions(expression, transitionPlan, context),
|
|
514
|
+
valid: true,
|
|
515
|
+
errors: []
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
const transitionError = new ConversionError(`Failed to handle undefined-to-defined transitions: ${error instanceof Error ? error.message : String(error)}`, String(expression), 'unknown');
|
|
520
|
+
return {
|
|
521
|
+
transitionPlan: { phases: [], totalDuration: 0, criticalFields: [] },
|
|
522
|
+
phaseExpressions: new Map(),
|
|
523
|
+
watchExpressions: [],
|
|
524
|
+
fallbackExpressions: new Map(),
|
|
525
|
+
valid: false,
|
|
526
|
+
errors: [transitionError]
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Analyze hydration states for KubernetesRef objects
|
|
532
|
+
*/
|
|
533
|
+
analyzeHydrationStates(kubernetesRefs, hydrationStates) {
|
|
534
|
+
const unhydratedRefs = [];
|
|
535
|
+
const hydratedRefs = [];
|
|
536
|
+
const hydratingRefs = [];
|
|
537
|
+
const failedRefs = [];
|
|
538
|
+
for (const ref of kubernetesRefs) {
|
|
539
|
+
const stateKey = `${ref.resourceId}:${ref.fieldPath}`;
|
|
540
|
+
const state = hydrationStates.get(stateKey);
|
|
541
|
+
if (!state) {
|
|
542
|
+
unhydratedRefs.push(ref);
|
|
543
|
+
}
|
|
544
|
+
else if (state.hydrationFailed) {
|
|
545
|
+
failedRefs.push(ref);
|
|
546
|
+
}
|
|
547
|
+
else if (state.isHydrated) {
|
|
548
|
+
hydratedRefs.push(ref);
|
|
549
|
+
}
|
|
550
|
+
else if (state.isHydrating) {
|
|
551
|
+
hydratingRefs.push(ref);
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
unhydratedRefs.push(ref);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
unhydratedRefs,
|
|
559
|
+
hydratedRefs,
|
|
560
|
+
hydratingRefs,
|
|
561
|
+
failedRefs,
|
|
562
|
+
totalRefs: kubernetesRefs.length,
|
|
563
|
+
hydrationProgress: hydratedRefs.length / kubernetesRefs.length
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Create transition plan for hydration phases
|
|
568
|
+
*/
|
|
569
|
+
createTransitionPlan(kubernetesRefs, _hydrationStates, _context) {
|
|
570
|
+
const phases = [];
|
|
571
|
+
const criticalFields = [];
|
|
572
|
+
// Group fields by expected hydration timing
|
|
573
|
+
const immediateFields = [];
|
|
574
|
+
const earlyFields = [];
|
|
575
|
+
const lateFields = [];
|
|
576
|
+
for (const ref of kubernetesRefs) {
|
|
577
|
+
const fieldPath = ref.fieldPath || '';
|
|
578
|
+
if (ref.resourceId === '__schema__' || fieldPath.startsWith('metadata.') || fieldPath.startsWith('spec.')) {
|
|
579
|
+
immediateFields.push(ref);
|
|
580
|
+
}
|
|
581
|
+
else if (fieldPath.includes('ready') || fieldPath.includes('available') || fieldPath.includes('replicas')) {
|
|
582
|
+
earlyFields.push(ref);
|
|
583
|
+
if (fieldPath.includes('ready') || fieldPath.includes('available')) {
|
|
584
|
+
criticalFields.push(`${ref.resourceId}.${fieldPath}`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
lateFields.push(ref);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
// Create phases
|
|
592
|
+
if (immediateFields.length > 0) {
|
|
593
|
+
phases.push({
|
|
594
|
+
name: 'immediate',
|
|
595
|
+
fields: immediateFields,
|
|
596
|
+
expectedDuration: 0,
|
|
597
|
+
dependencies: [],
|
|
598
|
+
isCritical: false
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
if (earlyFields.length > 0) {
|
|
602
|
+
phases.push({
|
|
603
|
+
name: 'early',
|
|
604
|
+
fields: earlyFields,
|
|
605
|
+
expectedDuration: 5000, // 5 seconds
|
|
606
|
+
dependencies: immediateFields.map(ref => `${ref.resourceId}.${ref.fieldPath}`),
|
|
607
|
+
isCritical: true
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
if (lateFields.length > 0) {
|
|
611
|
+
phases.push({
|
|
612
|
+
name: 'late',
|
|
613
|
+
fields: lateFields,
|
|
614
|
+
expectedDuration: 30000, // 30 seconds
|
|
615
|
+
dependencies: [...immediateFields, ...earlyFields].map(ref => `${ref.resourceId}.${ref.fieldPath}`),
|
|
616
|
+
isCritical: false
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
const totalDuration = phases.reduce((sum, phase) => sum + phase.expectedDuration, 0);
|
|
620
|
+
return { phases, totalDuration, criticalFields };
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Generate hydration transition handlers
|
|
624
|
+
*/
|
|
625
|
+
generateHydrationTransitionHandlers(expression, hydrationAnalysis, context) {
|
|
626
|
+
const handlers = [];
|
|
627
|
+
// Handler for unhydrated -> hydrating transition
|
|
628
|
+
if (hydrationAnalysis.unhydratedRefs.length > 0) {
|
|
629
|
+
handlers.push({
|
|
630
|
+
fromState: 'unhydrated',
|
|
631
|
+
toState: 'hydrating',
|
|
632
|
+
triggerCondition: this.generateHydrationStartCondition(hydrationAnalysis.unhydratedRefs),
|
|
633
|
+
transitionExpression: this.generateHydrationStartExpression(expression, hydrationAnalysis.unhydratedRefs, context),
|
|
634
|
+
priority: 1
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
// Handler for hydrating -> hydrated transition
|
|
638
|
+
if (hydrationAnalysis.hydratingRefs.length > 0) {
|
|
639
|
+
handlers.push({
|
|
640
|
+
fromState: 'hydrating',
|
|
641
|
+
toState: 'hydrated',
|
|
642
|
+
triggerCondition: this.generateHydrationCompleteCondition(hydrationAnalysis.hydratingRefs),
|
|
643
|
+
transitionExpression: this.generateHydrationCompleteExpression(expression, hydrationAnalysis.hydratingRefs, context),
|
|
644
|
+
priority: 2
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
// Handler for hydration failure
|
|
648
|
+
if (hydrationAnalysis.failedRefs.length > 0) {
|
|
649
|
+
handlers.push({
|
|
650
|
+
fromState: 'hydrating',
|
|
651
|
+
toState: 'failed',
|
|
652
|
+
triggerCondition: this.generateHydrationFailureCondition(hydrationAnalysis.failedRefs),
|
|
653
|
+
transitionExpression: this.generateHydrationFailureExpression(expression, hydrationAnalysis.failedRefs, context),
|
|
654
|
+
priority: 3
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
return handlers;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Generate phase expressions for different hydration phases
|
|
661
|
+
*/
|
|
662
|
+
generatePhaseExpressions(expression, transitionPlan, context) {
|
|
663
|
+
const phaseExpressions = new Map();
|
|
664
|
+
for (const phase of transitionPlan.phases) {
|
|
665
|
+
try {
|
|
666
|
+
const phaseExpression = this.generatePhaseSpecificExpression(expression, phase, context);
|
|
667
|
+
phaseExpressions.set(phase.name, phaseExpression);
|
|
668
|
+
}
|
|
669
|
+
catch (error) {
|
|
670
|
+
this.logger.warn(`Failed to generate expression for phase ${phase.name}`, error);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return phaseExpressions;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Generate watch expressions for monitoring hydration progress
|
|
677
|
+
*/
|
|
678
|
+
generateWatchExpressions(transitionPlan, _context) {
|
|
679
|
+
const watchExpressions = [];
|
|
680
|
+
for (const phase of transitionPlan.phases) {
|
|
681
|
+
for (const field of phase.fields) {
|
|
682
|
+
const resourcePath = field.resourceId === '__schema__'
|
|
683
|
+
? `schema.${field.fieldPath}`
|
|
684
|
+
: `resources.${field.resourceId}.${field.fieldPath}`;
|
|
685
|
+
const watchExpression = {
|
|
686
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
687
|
+
expression: `has(${resourcePath})`,
|
|
688
|
+
type: 'boolean'
|
|
689
|
+
};
|
|
690
|
+
watchExpressions.push(watchExpression);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return watchExpressions;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Generate fallback expressions for hydration failures
|
|
697
|
+
*/
|
|
698
|
+
generateFallbackExpressions(_expression, transitionPlan, _context) {
|
|
699
|
+
const fallbackExpressions = new Map();
|
|
700
|
+
for (const phase of transitionPlan.phases) {
|
|
701
|
+
const fallbackExpression = {
|
|
702
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
703
|
+
expression: phase.isCritical ? 'false' : 'null',
|
|
704
|
+
type: phase.isCritical ? 'boolean' : 'null'
|
|
705
|
+
};
|
|
706
|
+
fallbackExpressions.set(phase.name, fallbackExpression);
|
|
707
|
+
}
|
|
708
|
+
return fallbackExpressions;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Generate phase-specific expression
|
|
712
|
+
*/
|
|
713
|
+
generatePhaseSpecificExpression(_expression, phase, _context) {
|
|
714
|
+
// Generate expression that only uses fields available in this phase
|
|
715
|
+
const availableFields = phase.fields.map(field => {
|
|
716
|
+
const resourcePath = field.resourceId === '__schema__'
|
|
717
|
+
? `schema.${field.fieldPath}`
|
|
718
|
+
: `resources.${field.resourceId}.${field.fieldPath}`;
|
|
719
|
+
return resourcePath;
|
|
720
|
+
});
|
|
721
|
+
// Create a simplified expression using only available fields
|
|
722
|
+
const phaseExpression = availableFields.length > 0
|
|
723
|
+
? availableFields.join(' && ')
|
|
724
|
+
: 'true';
|
|
725
|
+
return {
|
|
726
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
727
|
+
expression: phaseExpression,
|
|
728
|
+
type: 'boolean'
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Generate condition for hydration start
|
|
733
|
+
*/
|
|
734
|
+
generateHydrationStartCondition(refs) {
|
|
735
|
+
const conditions = refs.map(ref => {
|
|
736
|
+
const resourcePath = ref.resourceId === '__schema__'
|
|
737
|
+
? `schema.${ref.fieldPath}`
|
|
738
|
+
: `resources.${ref.resourceId}.${ref.fieldPath}`;
|
|
739
|
+
return `!has(${resourcePath})`;
|
|
740
|
+
});
|
|
741
|
+
return conditions.join(' && ');
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Generate expression for hydration start
|
|
745
|
+
*/
|
|
746
|
+
generateHydrationStartExpression(_expression, _refs, _context) {
|
|
747
|
+
return {
|
|
748
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
749
|
+
expression: 'null', // Return null while hydrating
|
|
750
|
+
type: 'null'
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Generate condition for hydration complete
|
|
755
|
+
*/
|
|
756
|
+
generateHydrationCompleteCondition(refs) {
|
|
757
|
+
const conditions = refs.map(ref => {
|
|
758
|
+
const resourcePath = ref.resourceId === '__schema__'
|
|
759
|
+
? `schema.${ref.fieldPath}`
|
|
760
|
+
: `resources.${ref.resourceId}.${ref.fieldPath}`;
|
|
761
|
+
return `has(${resourcePath})`;
|
|
762
|
+
});
|
|
763
|
+
return conditions.join(' && ');
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Generate expression for hydration complete
|
|
767
|
+
*/
|
|
768
|
+
generateHydrationCompleteExpression(expression, _refs, context) {
|
|
769
|
+
// Use the original expression since all fields are now available
|
|
770
|
+
return this.convertToBasicCel(expression, context);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Generate condition for hydration failure
|
|
774
|
+
*/
|
|
775
|
+
generateHydrationFailureCondition(_refs) {
|
|
776
|
+
// This would typically check for timeout or error conditions
|
|
777
|
+
return 'false'; // Placeholder
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Generate expression for hydration failure
|
|
781
|
+
*/
|
|
782
|
+
generateHydrationFailureExpression(_expression, _refs, _context) {
|
|
783
|
+
return {
|
|
784
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
785
|
+
expression: 'false', // Return false on failure
|
|
786
|
+
type: 'boolean'
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Analyze a single KubernetesRef for optionality requirements
|
|
791
|
+
*/
|
|
792
|
+
analyzeKubernetesRefOptionality(kubernetesRef, context) {
|
|
793
|
+
const isSchemaReference = kubernetesRef.resourceId === '__schema__';
|
|
794
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
795
|
+
// Enhanced types appear non-optional at compile time but may be undefined at runtime
|
|
796
|
+
const potentiallyUndefined = this.isPotentiallyUndefinedAtRuntime(kubernetesRef, context);
|
|
797
|
+
const requiresNullSafety = potentiallyUndefined && (context.conservativeNullSafety ?? true);
|
|
798
|
+
// Check if optional chaining was used in the original expression
|
|
799
|
+
const hasOptionalChaining = this.hasOptionalChainingInExpression(kubernetesRef, context);
|
|
800
|
+
const confidence = this.calculateOptionalityConfidence(kubernetesRef, context);
|
|
801
|
+
const reason = this.determineOptionalityReason(kubernetesRef, context);
|
|
802
|
+
const suggestedCelPattern = requiresNullSafety
|
|
803
|
+
? this.generateSuggestedCelPattern(kubernetesRef, context)
|
|
804
|
+
: undefined;
|
|
805
|
+
return {
|
|
806
|
+
kubernetesRef,
|
|
807
|
+
potentiallyUndefined,
|
|
808
|
+
requiresNullSafety,
|
|
809
|
+
hasOptionalChaining,
|
|
810
|
+
fieldPath,
|
|
811
|
+
resourceId: kubernetesRef.resourceId,
|
|
812
|
+
isSchemaReference,
|
|
813
|
+
confidence,
|
|
814
|
+
reason,
|
|
815
|
+
suggestedCelPattern
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Determine if a KubernetesRef is potentially undefined at runtime
|
|
820
|
+
*/
|
|
821
|
+
isPotentiallyUndefinedAtRuntime(kubernetesRef, context) {
|
|
822
|
+
// Schema references are generally available, but some schema fields might be optional
|
|
823
|
+
if (kubernetesRef.resourceId === '__schema__') {
|
|
824
|
+
return this.isSchemaFieldPotentiallyUndefined(kubernetesRef, context);
|
|
825
|
+
}
|
|
826
|
+
// Resource status fields are potentially undefined during field hydration
|
|
827
|
+
if (kubernetesRef.fieldPath?.startsWith('status.')) {
|
|
828
|
+
return this.isStatusFieldPotentiallyUndefined(kubernetesRef, context);
|
|
829
|
+
}
|
|
830
|
+
// Resource spec fields might be optional
|
|
831
|
+
if (kubernetesRef.fieldPath?.startsWith('spec.')) {
|
|
832
|
+
return this.isSpecFieldPotentiallyUndefined(kubernetesRef, context);
|
|
833
|
+
}
|
|
834
|
+
// Resource metadata fields are generally available but some might be optional
|
|
835
|
+
if (kubernetesRef.fieldPath?.startsWith('metadata.')) {
|
|
836
|
+
return this.isMetadataFieldPotentiallyUndefined(kubernetesRef, context);
|
|
837
|
+
}
|
|
838
|
+
// Check hydration state if available
|
|
839
|
+
if (context.hydrationStates) {
|
|
840
|
+
const stateKey = `${kubernetesRef.resourceId}:${kubernetesRef.fieldPath}`;
|
|
841
|
+
const state = context.hydrationStates.get(stateKey);
|
|
842
|
+
if (state) {
|
|
843
|
+
return !state.isHydrated || state.hydrationFailed;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
// Conservative approach: assume potentially undefined for Enhanced types
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Check if a schema field is potentially undefined
|
|
851
|
+
*/
|
|
852
|
+
isSchemaFieldPotentiallyUndefined(kubernetesRef, context) {
|
|
853
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
854
|
+
// Common optional schema fields
|
|
855
|
+
const commonOptionalFields = [
|
|
856
|
+
'metadata.labels',
|
|
857
|
+
'metadata.annotations',
|
|
858
|
+
'metadata.namespace',
|
|
859
|
+
'spec.replicas',
|
|
860
|
+
'spec.resources',
|
|
861
|
+
'spec.nodeSelector',
|
|
862
|
+
'spec.tolerations',
|
|
863
|
+
'spec.affinity'
|
|
864
|
+
];
|
|
865
|
+
// Check if this is a commonly optional field
|
|
866
|
+
if (commonOptionalFields.some(optional => fieldPath.startsWith(optional))) {
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
// Check for array access which might be undefined
|
|
870
|
+
if (fieldPath.includes('[') || fieldPath.includes('.length')) {
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
// Schema fields are generally available, but be conservative
|
|
874
|
+
return context.conservativeNullSafety ?? true;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Check if a status field is potentially undefined
|
|
878
|
+
*/
|
|
879
|
+
isStatusFieldPotentiallyUndefined(kubernetesRef, _context) {
|
|
880
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
881
|
+
// Status fields are almost always potentially undefined during hydration
|
|
882
|
+
const alwaysUndefinedStatusFields = [
|
|
883
|
+
'status.conditions',
|
|
884
|
+
'status.loadBalancer',
|
|
885
|
+
'status.ingress',
|
|
886
|
+
'status.podIP',
|
|
887
|
+
'status.hostIP',
|
|
888
|
+
'status.phase',
|
|
889
|
+
'status.readyReplicas',
|
|
890
|
+
'status.availableReplicas',
|
|
891
|
+
'status.observedGeneration'
|
|
892
|
+
];
|
|
893
|
+
// Check if this is a field that's commonly undefined
|
|
894
|
+
if (alwaysUndefinedStatusFields.some(field => fieldPath.startsWith(field))) {
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
897
|
+
// All status fields are potentially undefined during field hydration
|
|
898
|
+
return true;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Check if a spec field is potentially undefined
|
|
902
|
+
*/
|
|
903
|
+
isSpecFieldPotentiallyUndefined(kubernetesRef, context) {
|
|
904
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
905
|
+
// Common optional spec fields
|
|
906
|
+
const commonOptionalSpecFields = [
|
|
907
|
+
'spec.replicas',
|
|
908
|
+
'spec.resources',
|
|
909
|
+
'spec.nodeSelector',
|
|
910
|
+
'spec.tolerations',
|
|
911
|
+
'spec.affinity',
|
|
912
|
+
'spec.volumes',
|
|
913
|
+
'spec.volumeMounts',
|
|
914
|
+
'spec.env',
|
|
915
|
+
'spec.ports',
|
|
916
|
+
'spec.selector'
|
|
917
|
+
];
|
|
918
|
+
// Check if this is a commonly optional spec field
|
|
919
|
+
if (commonOptionalSpecFields.some(optional => fieldPath.startsWith(optional))) {
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
// Check for array access
|
|
923
|
+
if (fieldPath.includes('[') || fieldPath.includes('.length')) {
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
// Most spec fields are required, but be conservative for Enhanced types
|
|
927
|
+
return context.conservativeNullSafety ?? false;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Check if a metadata field is potentially undefined
|
|
931
|
+
*/
|
|
932
|
+
isMetadataFieldPotentiallyUndefined(kubernetesRef, context) {
|
|
933
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
934
|
+
// Common optional metadata fields
|
|
935
|
+
const commonOptionalMetadataFields = [
|
|
936
|
+
'metadata.labels',
|
|
937
|
+
'metadata.annotations',
|
|
938
|
+
'metadata.namespace',
|
|
939
|
+
'metadata.ownerReferences',
|
|
940
|
+
'metadata.finalizers'
|
|
941
|
+
];
|
|
942
|
+
// Check if this is a commonly optional metadata field
|
|
943
|
+
if (commonOptionalMetadataFields.some(optional => fieldPath.startsWith(optional))) {
|
|
944
|
+
return true;
|
|
945
|
+
}
|
|
946
|
+
// Core metadata fields like name and uid are generally available
|
|
947
|
+
const coreMetadataFields = [
|
|
948
|
+
'metadata.name',
|
|
949
|
+
'metadata.uid',
|
|
950
|
+
'metadata.creationTimestamp',
|
|
951
|
+
'metadata.generation'
|
|
952
|
+
];
|
|
953
|
+
if (coreMetadataFields.some(core => fieldPath.startsWith(core))) {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
// Be conservative for other metadata fields
|
|
957
|
+
return context.conservativeNullSafety ?? true;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Check if optional chaining was used in the original expression
|
|
961
|
+
*/
|
|
962
|
+
hasOptionalChainingInExpression(_kubernetesRef, _context) {
|
|
963
|
+
// This would need to be determined from the original expression AST
|
|
964
|
+
// For now, return false as a placeholder
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Calculate confidence level for optionality analysis
|
|
969
|
+
*/
|
|
970
|
+
calculateOptionalityConfidence(kubernetesRef, context) {
|
|
971
|
+
let confidence = 0.8; // Base confidence
|
|
972
|
+
// Higher confidence for schema references
|
|
973
|
+
if (kubernetesRef.resourceId === '__schema__') {
|
|
974
|
+
confidence += 0.1;
|
|
975
|
+
}
|
|
976
|
+
// Lower confidence for status fields (more likely to be undefined)
|
|
977
|
+
if (kubernetesRef.fieldPath?.startsWith('status.')) {
|
|
978
|
+
confidence -= 0.2;
|
|
979
|
+
}
|
|
980
|
+
// Higher confidence if we have hydration state information
|
|
981
|
+
if (context.hydrationStates) {
|
|
982
|
+
confidence += 0.1;
|
|
983
|
+
}
|
|
984
|
+
return Math.max(0, Math.min(1, confidence));
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Determine the reason for optionality determination
|
|
988
|
+
*/
|
|
989
|
+
determineOptionalityReason(kubernetesRef, _context) {
|
|
990
|
+
if (kubernetesRef.resourceId === '__schema__') {
|
|
991
|
+
return 'Schema reference - generally available';
|
|
992
|
+
}
|
|
993
|
+
if (kubernetesRef.fieldPath?.startsWith('status.')) {
|
|
994
|
+
return 'Status field - potentially undefined during field hydration';
|
|
995
|
+
}
|
|
996
|
+
return 'Enhanced type - appears non-optional at compile time but may be undefined at runtime';
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Generate suggested CEL pattern for null-safety
|
|
1000
|
+
*/
|
|
1001
|
+
generateSuggestedCelPattern(kubernetesRef, context) {
|
|
1002
|
+
const resourcePath = kubernetesRef.resourceId === '__schema__'
|
|
1003
|
+
? `schema.${kubernetesRef.fieldPath}`
|
|
1004
|
+
: `resources.${kubernetesRef.resourceId}.${kubernetesRef.fieldPath}`;
|
|
1005
|
+
if (context.generateHasChecks) {
|
|
1006
|
+
return `has(${resourcePath}) && ${resourcePath}`;
|
|
1007
|
+
}
|
|
1008
|
+
if (context.useKroConditionals) {
|
|
1009
|
+
return `${resourcePath}?`;
|
|
1010
|
+
}
|
|
1011
|
+
return resourcePath;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Extract KubernetesRef objects from an expression
|
|
1015
|
+
*/
|
|
1016
|
+
extractKubernetesRefs(expression) {
|
|
1017
|
+
const refs = [];
|
|
1018
|
+
if (isKubernetesRef(expression)) {
|
|
1019
|
+
refs.push(expression);
|
|
1020
|
+
}
|
|
1021
|
+
else if (Array.isArray(expression)) {
|
|
1022
|
+
for (const item of expression) {
|
|
1023
|
+
refs.push(...this.extractKubernetesRefs(item));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
else if (expression && typeof expression === 'object') {
|
|
1027
|
+
for (const value of Object.values(expression)) {
|
|
1028
|
+
refs.push(...this.extractKubernetesRefs(value));
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return refs;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Convert expression to basic CEL without null-safety
|
|
1035
|
+
*/
|
|
1036
|
+
convertToBasicCel(expression, _context) {
|
|
1037
|
+
// This is a placeholder - would need to integrate with the main analyzer
|
|
1038
|
+
return {
|
|
1039
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
1040
|
+
expression: String(expression),
|
|
1041
|
+
type: 'unknown'
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Generate CEL expressions with has() checks for potentially undefined fields
|
|
1046
|
+
*
|
|
1047
|
+
* This method creates comprehensive CEL expressions that include has() checks
|
|
1048
|
+
* for all potentially undefined fields in the expression.
|
|
1049
|
+
*/
|
|
1050
|
+
generateCelWithHasChecks(expression, optionalityResults, context) {
|
|
1051
|
+
try {
|
|
1052
|
+
const fieldsRequiringChecks = optionalityResults.filter(result => result.requiresNullSafety);
|
|
1053
|
+
if (fieldsRequiringChecks.length === 0) {
|
|
1054
|
+
return this.convertToBasicCel(expression, context);
|
|
1055
|
+
}
|
|
1056
|
+
// Generate has() checks for each field
|
|
1057
|
+
const hasChecks = this.generateHasChecksForFields(fieldsRequiringChecks, context);
|
|
1058
|
+
// Generate the main expression
|
|
1059
|
+
const mainExpression = this.convertExpressionWithKubernetesRefs(expression, optionalityResults, context);
|
|
1060
|
+
// Combine has() checks with the main expression
|
|
1061
|
+
const combinedExpression = this.combineHasChecksWithExpression(hasChecks, mainExpression, context);
|
|
1062
|
+
return {
|
|
1063
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
1064
|
+
expression: combinedExpression,
|
|
1065
|
+
type: this.inferExpressionType(expression, context)
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
catch (error) {
|
|
1069
|
+
this.logger.error('Failed to generate CEL with has() checks', error);
|
|
1070
|
+
return this.convertToBasicCel(expression, context);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Generate has() checks for fields that require null-safety
|
|
1075
|
+
*/
|
|
1076
|
+
generateHasChecksForFields(fieldsRequiringChecks, context) {
|
|
1077
|
+
const hasChecks = [];
|
|
1078
|
+
const processedPaths = new Set();
|
|
1079
|
+
for (const field of fieldsRequiringChecks) {
|
|
1080
|
+
const resourcePath = field.isSchemaReference
|
|
1081
|
+
? `schema.${field.fieldPath}`
|
|
1082
|
+
: `resources.${field.resourceId}.${field.fieldPath}`;
|
|
1083
|
+
// Avoid duplicate checks for the same path
|
|
1084
|
+
if (processedPaths.has(resourcePath)) {
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
processedPaths.add(resourcePath);
|
|
1088
|
+
// Generate nested has() checks for complex field paths
|
|
1089
|
+
const nestedChecks = this.generateNestedHasChecks(field, context);
|
|
1090
|
+
hasChecks.push(...nestedChecks);
|
|
1091
|
+
}
|
|
1092
|
+
return hasChecks;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Generate nested has() checks for complex field paths
|
|
1096
|
+
*/
|
|
1097
|
+
generateNestedHasChecks(field, _context) {
|
|
1098
|
+
const checks = [];
|
|
1099
|
+
const fieldPath = field.fieldPath;
|
|
1100
|
+
if (!fieldPath || !fieldPath.includes('.')) {
|
|
1101
|
+
// Simple field path
|
|
1102
|
+
const resourcePath = field.isSchemaReference
|
|
1103
|
+
? `schema.${fieldPath}`
|
|
1104
|
+
: `resources.${field.resourceId}.${fieldPath}`;
|
|
1105
|
+
checks.push(`has(${resourcePath})`);
|
|
1106
|
+
return checks;
|
|
1107
|
+
}
|
|
1108
|
+
// Complex field path - check each level
|
|
1109
|
+
const pathParts = fieldPath.split('.');
|
|
1110
|
+
const basePrefix = field.isSchemaReference ? 'schema' : `resources.${field.resourceId}`;
|
|
1111
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
1112
|
+
const partialPath = pathParts.slice(0, i + 1).join('.');
|
|
1113
|
+
const fullPath = `${basePrefix}.${partialPath}`;
|
|
1114
|
+
// Skip checks for array indices
|
|
1115
|
+
if (!partialPath.includes('[') && !partialPath.includes(']')) {
|
|
1116
|
+
checks.push(`has(${fullPath})`);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return checks;
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Convert expression with KubernetesRef objects to CEL
|
|
1123
|
+
*/
|
|
1124
|
+
convertExpressionWithKubernetesRefs(expression, optionalityResults, context) {
|
|
1125
|
+
// This is a simplified conversion - in a real implementation,
|
|
1126
|
+
// this would integrate with the main expression analyzer
|
|
1127
|
+
if (isKubernetesRef(expression)) {
|
|
1128
|
+
const result = optionalityResults.find(r => r.kubernetesRef === expression);
|
|
1129
|
+
if (result) {
|
|
1130
|
+
return result.isSchemaReference
|
|
1131
|
+
? `schema.${result.fieldPath}`
|
|
1132
|
+
: `resources.${result.resourceId}.${result.fieldPath}`;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
// Handle different expression types
|
|
1136
|
+
if (typeof expression === 'string') {
|
|
1137
|
+
return `"${expression}"`;
|
|
1138
|
+
}
|
|
1139
|
+
if (typeof expression === 'number') {
|
|
1140
|
+
return String(expression);
|
|
1141
|
+
}
|
|
1142
|
+
if (typeof expression === 'boolean') {
|
|
1143
|
+
return String(expression);
|
|
1144
|
+
}
|
|
1145
|
+
if (Array.isArray(expression)) {
|
|
1146
|
+
const elements = expression.map(item => this.convertExpressionWithKubernetesRefs(item, optionalityResults, context));
|
|
1147
|
+
return `[${elements.join(', ')}]`;
|
|
1148
|
+
}
|
|
1149
|
+
if (expression && typeof expression === 'object') {
|
|
1150
|
+
// Handle object expressions
|
|
1151
|
+
const properties = Object.entries(expression).map(([key, value]) => {
|
|
1152
|
+
const convertedValue = this.convertExpressionWithKubernetesRefs(value, optionalityResults, context);
|
|
1153
|
+
return `"${key}": ${convertedValue}`;
|
|
1154
|
+
});
|
|
1155
|
+
return `{${properties.join(', ')}}`;
|
|
1156
|
+
}
|
|
1157
|
+
return String(expression);
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Combine has() checks with the main expression
|
|
1161
|
+
*/
|
|
1162
|
+
combineHasChecksWithExpression(hasChecks, mainExpression, _context) {
|
|
1163
|
+
if (hasChecks.length === 0) {
|
|
1164
|
+
return mainExpression;
|
|
1165
|
+
}
|
|
1166
|
+
// Remove duplicate checks
|
|
1167
|
+
const uniqueChecks = Array.from(new Set(hasChecks));
|
|
1168
|
+
// Combine all checks with AND operator
|
|
1169
|
+
const allChecks = uniqueChecks.join(' && ');
|
|
1170
|
+
// Combine checks with the main expression
|
|
1171
|
+
return `${allChecks} && ${mainExpression}`;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Infer the type of the expression result
|
|
1175
|
+
*/
|
|
1176
|
+
inferExpressionType(expression, _context) {
|
|
1177
|
+
if (typeof expression === 'string') {
|
|
1178
|
+
return 'string';
|
|
1179
|
+
}
|
|
1180
|
+
if (typeof expression === 'number') {
|
|
1181
|
+
return 'number';
|
|
1182
|
+
}
|
|
1183
|
+
if (typeof expression === 'boolean') {
|
|
1184
|
+
return 'boolean';
|
|
1185
|
+
}
|
|
1186
|
+
if (Array.isArray(expression)) {
|
|
1187
|
+
return 'array';
|
|
1188
|
+
}
|
|
1189
|
+
if (expression && typeof expression === 'object') {
|
|
1190
|
+
return 'object';
|
|
1191
|
+
}
|
|
1192
|
+
return 'unknown';
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Generate null-safe CEL expression
|
|
1196
|
+
*/
|
|
1197
|
+
generateNullSafeExpression(expression, optionalityResults, context) {
|
|
1198
|
+
// Use the enhanced has() check generation
|
|
1199
|
+
return this.generateCelWithHasChecks(expression, optionalityResults, context);
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Generate source mapping for debugging
|
|
1203
|
+
*/
|
|
1204
|
+
generateSourceMapping(originalExpression, celExpression, context) {
|
|
1205
|
+
if (!context.sourceMap) {
|
|
1206
|
+
return [];
|
|
1207
|
+
}
|
|
1208
|
+
return [{
|
|
1209
|
+
originalExpression: String(originalExpression),
|
|
1210
|
+
celExpression: celExpression.expression,
|
|
1211
|
+
sourceLocation: {
|
|
1212
|
+
line: 0,
|
|
1213
|
+
column: 0,
|
|
1214
|
+
length: String(originalExpression).length
|
|
1215
|
+
},
|
|
1216
|
+
context: 'status',
|
|
1217
|
+
id: `optionality-${Date.now()}`,
|
|
1218
|
+
timestamp: Date.now()
|
|
1219
|
+
}];
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Generate pre-hydration expression (for unhydrated fields)
|
|
1223
|
+
*/
|
|
1224
|
+
generatePreHydrationExpression(_expression, _unhydratedRefs, _context) {
|
|
1225
|
+
// For pre-hydration, return a safe default or null check
|
|
1226
|
+
return {
|
|
1227
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
1228
|
+
expression: 'false', // Safe default before hydration
|
|
1229
|
+
type: 'boolean'
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Generate post-hydration expression (for hydrated fields)
|
|
1234
|
+
*/
|
|
1235
|
+
generatePostHydrationExpression(expression, _hydratedRefs, context) {
|
|
1236
|
+
// For post-hydration, can use the fields directly
|
|
1237
|
+
return this.convertToBasicCel(expression, context);
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Generate hydration-dependent expression (for fields being hydrated)
|
|
1241
|
+
*/
|
|
1242
|
+
generateHydrationDependentExpression(_expression, hydratingRefs, _context) {
|
|
1243
|
+
// For fields being hydrated, use conditional checks
|
|
1244
|
+
const conditionalChecks = hydratingRefs.map(ref => {
|
|
1245
|
+
const resourcePath = ref.resourceId === '__schema__'
|
|
1246
|
+
? `schema.${ref.fieldPath}`
|
|
1247
|
+
: `resources.${ref.resourceId}.${ref.fieldPath}`;
|
|
1248
|
+
return `has(${resourcePath})`;
|
|
1249
|
+
}).join(' && ');
|
|
1250
|
+
return {
|
|
1251
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
1252
|
+
expression: conditionalChecks,
|
|
1253
|
+
type: 'boolean'
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Convenience function to analyze optionality requirements
|
|
1259
|
+
*/
|
|
1260
|
+
export function analyzeOptionalityRequirements(expression, context, options) {
|
|
1261
|
+
const handler = new EnhancedTypeOptionalityHandler(options);
|
|
1262
|
+
return handler.analyzeOptionalityRequirements(expression, context);
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Convenience function to generate null-safe CEL expressions
|
|
1266
|
+
*/
|
|
1267
|
+
export function generateNullSafeCelExpression(expression, optionalityResults, context, options) {
|
|
1268
|
+
const handler = new EnhancedTypeOptionalityHandler(options);
|
|
1269
|
+
return handler.generateNullSafeCelExpression(expression, optionalityResults, context);
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Convenience function to handle optional chaining with Enhanced types
|
|
1273
|
+
*/
|
|
1274
|
+
export function handleOptionalChainingWithEnhancedTypes(expression, context, options) {
|
|
1275
|
+
const handler = new EnhancedTypeOptionalityHandler(options);
|
|
1276
|
+
return handler.handleOptionalChainingWithEnhancedTypes(expression, context);
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Convenience function to generate CEL expressions with has() checks
|
|
1280
|
+
*/
|
|
1281
|
+
export function generateCelWithHasChecks(expression, optionalityResults, context, options) {
|
|
1282
|
+
const handler = new EnhancedTypeOptionalityHandler(options);
|
|
1283
|
+
return handler.generateCelWithHasChecks(expression, optionalityResults, context);
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Convenience function to detect null-safety requirements for Enhanced types
|
|
1287
|
+
*/
|
|
1288
|
+
export function detectNullSafetyRequirements(enhancedResources, context, options) {
|
|
1289
|
+
const handler = new EnhancedTypeOptionalityHandler(options);
|
|
1290
|
+
return handler.detectNullSafetyRequirements(enhancedResources, context);
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Convenience function to integrate with field hydration timing
|
|
1294
|
+
*/
|
|
1295
|
+
export function integrateWithFieldHydrationTiming(expression, hydrationStates, context, options) {
|
|
1296
|
+
const handler = new EnhancedTypeOptionalityHandler(options);
|
|
1297
|
+
return handler.integrateWithFieldHydrationTiming(expression, hydrationStates, context);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Convenience function to handle undefined-to-defined transitions
|
|
1301
|
+
*/
|
|
1302
|
+
export function handleUndefinedToDefinedTransitions(expression, hydrationStates, context, options) {
|
|
1303
|
+
const handler = new EnhancedTypeOptionalityHandler(options);
|
|
1304
|
+
return handler.handleUndefinedToDefinedTransitions(expression, hydrationStates, context);
|
|
1305
|
+
}
|
|
1306
|
+
//# sourceMappingURL=optionality-handler.js.map
|