typekro 0.2.2 → 0.3.1
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 +13 -1
- package/dist/core/deployment/engine.d.ts.map +1 -1
- package/dist/core/deployment/engine.js +48 -3
- 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/evaluation/cel-optimizer.d.ts.map +1 -1
- package/dist/core/evaluation/cel-optimizer.js +7 -13
- package/dist/core/evaluation/cel-optimizer.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 +457 -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 +1311 -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/references/cel.d.ts +13 -1
- package/dist/core/references/cel.d.ts.map +1 -1
- package/dist/core/references/cel.js +50 -0
- package/dist/core/references/cel.js.map +1 -1
- package/dist/core/references/schema-proxy.d.ts.map +1 -1
- package/dist/core/references/schema-proxy.js +13 -8
- package/dist/core/references/schema-proxy.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 +7 -0
- package/dist/core/types/deployment.d.ts.map +1 -1
- package/dist/core/types/deployment.js +7 -0
- 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/validation/cel-validator.d.ts.map +1 -1
- package/dist/core/validation/cel-validator.js +4 -8
- package/dist/core/validation/cel-validator.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/index.js +1 -0
- package/dist/factories/index.js.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,2443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy Analysis Support for JavaScript to CEL Expression Conversion
|
|
3
|
+
*
|
|
4
|
+
* This module provides lazy evaluation capabilities for expressions containing
|
|
5
|
+
* KubernetesRef objects, optimizing performance by deferring analysis until
|
|
6
|
+
* the results are actually needed.
|
|
7
|
+
*/
|
|
8
|
+
import { JavaScriptToCelAnalyzer } from './analyzer.js';
|
|
9
|
+
import { containsKubernetesRefs, extractResourceReferences } from '../../utils/type-guards.js';
|
|
10
|
+
import { ConversionError } from '../errors.js';
|
|
11
|
+
import { KUBERNETES_REF_BRAND } from '../constants/brands.js';
|
|
12
|
+
/**
|
|
13
|
+
* Lazy wrapper for expressions that may contain KubernetesRef objects
|
|
14
|
+
* Defers analysis until the CEL expression is actually needed
|
|
15
|
+
*/
|
|
16
|
+
export class LazyAnalyzedExpression {
|
|
17
|
+
_expression;
|
|
18
|
+
_context;
|
|
19
|
+
_analyzed = false;
|
|
20
|
+
_result = null;
|
|
21
|
+
_error = null;
|
|
22
|
+
_analyzer;
|
|
23
|
+
constructor(_expression, _context, analyzer) {
|
|
24
|
+
this._expression = _expression;
|
|
25
|
+
this._context = _context;
|
|
26
|
+
this._analyzer = analyzer || new JavaScriptToCelAnalyzer();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the original expression without triggering analysis
|
|
30
|
+
*/
|
|
31
|
+
get originalExpression() {
|
|
32
|
+
return this._expression;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the analysis context
|
|
36
|
+
*/
|
|
37
|
+
get context() {
|
|
38
|
+
return this._context;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if the expression has been analyzed yet
|
|
42
|
+
*/
|
|
43
|
+
get isAnalyzed() {
|
|
44
|
+
return this._analyzed;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Check if the expression requires conversion (contains KubernetesRef objects)
|
|
48
|
+
* This is a fast check that doesn't trigger full analysis
|
|
49
|
+
*/
|
|
50
|
+
get requiresConversion() {
|
|
51
|
+
return containsKubernetesRefs(this._expression);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if the expression is static (no KubernetesRef objects)
|
|
55
|
+
* This is a fast check that doesn't trigger analysis
|
|
56
|
+
*/
|
|
57
|
+
get isStatic() {
|
|
58
|
+
return !this.requiresConversion;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the analysis result, triggering analysis if not already done
|
|
62
|
+
*/
|
|
63
|
+
get result() {
|
|
64
|
+
if (!this._analyzed) {
|
|
65
|
+
this._performAnalysis();
|
|
66
|
+
}
|
|
67
|
+
if (this._error) {
|
|
68
|
+
throw this._error;
|
|
69
|
+
}
|
|
70
|
+
return this._result;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get the CEL expression, triggering analysis if needed
|
|
74
|
+
*/
|
|
75
|
+
get celExpression() {
|
|
76
|
+
return this.result.celExpression;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get the dependencies, triggering analysis if needed
|
|
80
|
+
*/
|
|
81
|
+
get dependencies() {
|
|
82
|
+
return this.result.dependencies;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get conversion errors, triggering analysis if needed
|
|
86
|
+
*/
|
|
87
|
+
get errors() {
|
|
88
|
+
return this.result.errors;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if the analysis was successful, triggering analysis if needed
|
|
92
|
+
*/
|
|
93
|
+
get isValid() {
|
|
94
|
+
return this.result.valid;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Force analysis to occur immediately
|
|
98
|
+
*/
|
|
99
|
+
analyze() {
|
|
100
|
+
if (!this._analyzed) {
|
|
101
|
+
this._performAnalysis();
|
|
102
|
+
}
|
|
103
|
+
if (this._error) {
|
|
104
|
+
throw this._error;
|
|
105
|
+
}
|
|
106
|
+
return this._result;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Try to get the result without throwing errors
|
|
110
|
+
*/
|
|
111
|
+
tryGetResult() {
|
|
112
|
+
try {
|
|
113
|
+
const result = this.result;
|
|
114
|
+
return { success: true, result };
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Create a new lazy expression with a different context
|
|
125
|
+
*/
|
|
126
|
+
withContext(newContext) {
|
|
127
|
+
return new LazyAnalyzedExpression(this._expression, newContext, this._analyzer);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create a new lazy expression with a different analyzer
|
|
131
|
+
*/
|
|
132
|
+
withAnalyzer(analyzer) {
|
|
133
|
+
return new LazyAnalyzedExpression(this._expression, this._context, analyzer);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Perform the actual analysis
|
|
137
|
+
*/
|
|
138
|
+
_performAnalysis() {
|
|
139
|
+
try {
|
|
140
|
+
this._result = this._analyzer.analyzeExpressionWithRefs(this._expression, this._context);
|
|
141
|
+
this._analyzed = true;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
this._error = error instanceof Error ? error : new Error(String(error));
|
|
145
|
+
this._analyzed = true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Reset the analysis state (for testing or re-analysis)
|
|
150
|
+
*/
|
|
151
|
+
reset() {
|
|
152
|
+
this._analyzed = false;
|
|
153
|
+
this._result = null;
|
|
154
|
+
this._error = null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get a string representation for debugging
|
|
158
|
+
*/
|
|
159
|
+
toString() {
|
|
160
|
+
const exprStr = typeof this._expression === 'string'
|
|
161
|
+
? this._expression
|
|
162
|
+
: JSON.stringify(this._expression);
|
|
163
|
+
if (this._analyzed) {
|
|
164
|
+
if (this._error) {
|
|
165
|
+
return `LazyAnalyzedExpression(${exprStr}) [ERROR: ${this._error.message}]`;
|
|
166
|
+
}
|
|
167
|
+
return `LazyAnalyzedExpression(${exprStr}) [ANALYZED: ${this._result?.valid ? 'VALID' : 'INVALID'}]`;
|
|
168
|
+
}
|
|
169
|
+
return `LazyAnalyzedExpression(${exprStr}) [NOT ANALYZED]`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Collection of lazy analyzed expressions for batch processing
|
|
174
|
+
*/
|
|
175
|
+
export class LazyExpressionCollection {
|
|
176
|
+
_expressions = new Map();
|
|
177
|
+
_analyzer;
|
|
178
|
+
constructor(analyzer) {
|
|
179
|
+
this._analyzer = analyzer || new JavaScriptToCelAnalyzer();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Add an expression to the collection
|
|
183
|
+
*/
|
|
184
|
+
add(key, expression, context) {
|
|
185
|
+
const lazy = new LazyAnalyzedExpression(expression, context, this._analyzer);
|
|
186
|
+
this._expressions.set(key, lazy);
|
|
187
|
+
return lazy;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get a lazy expression by key
|
|
191
|
+
*/
|
|
192
|
+
get(key) {
|
|
193
|
+
return this._expressions.get(key);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Check if an expression exists
|
|
197
|
+
*/
|
|
198
|
+
has(key) {
|
|
199
|
+
return this._expressions.has(key);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Remove an expression
|
|
203
|
+
*/
|
|
204
|
+
remove(key) {
|
|
205
|
+
return this._expressions.delete(key);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Get all expression keys
|
|
209
|
+
*/
|
|
210
|
+
keys() {
|
|
211
|
+
return Array.from(this._expressions.keys());
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get all lazy expressions
|
|
215
|
+
*/
|
|
216
|
+
values() {
|
|
217
|
+
return Array.from(this._expressions.values());
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get all entries
|
|
221
|
+
*/
|
|
222
|
+
entries() {
|
|
223
|
+
return Array.from(this._expressions.entries());
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get the number of expressions
|
|
227
|
+
*/
|
|
228
|
+
get size() {
|
|
229
|
+
return this._expressions.size;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Check how many expressions require conversion
|
|
233
|
+
*/
|
|
234
|
+
get requiresConversionCount() {
|
|
235
|
+
return this.values().filter(expr => expr.requiresConversion).length;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Check how many expressions are static
|
|
239
|
+
*/
|
|
240
|
+
get staticCount() {
|
|
241
|
+
return this.values().filter(expr => expr.isStatic).length;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Check how many expressions have been analyzed
|
|
245
|
+
*/
|
|
246
|
+
get analyzedCount() {
|
|
247
|
+
return this.values().filter(expr => expr.isAnalyzed).length;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Analyze all expressions that require conversion
|
|
251
|
+
*/
|
|
252
|
+
analyzeAll() {
|
|
253
|
+
const results = new Map();
|
|
254
|
+
for (const [key, expr] of this._expressions) {
|
|
255
|
+
if (expr.requiresConversion) {
|
|
256
|
+
try {
|
|
257
|
+
results.set(key, expr.analyze());
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
// Create error result
|
|
261
|
+
results.set(key, {
|
|
262
|
+
valid: false,
|
|
263
|
+
celExpression: null,
|
|
264
|
+
dependencies: [],
|
|
265
|
+
sourceMap: [],
|
|
266
|
+
errors: [new ConversionError(error instanceof Error ? error.message : String(error), String(expr.originalExpression), 'unknown')],
|
|
267
|
+
warnings: [],
|
|
268
|
+
requiresConversion: true
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return results;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Analyze expressions in parallel (for independent expressions)
|
|
277
|
+
*/
|
|
278
|
+
async analyzeAllParallel() {
|
|
279
|
+
const promises = Array.from(this._expressions.entries())
|
|
280
|
+
.filter(([, expr]) => expr.requiresConversion)
|
|
281
|
+
.map(async ([key, expr]) => {
|
|
282
|
+
try {
|
|
283
|
+
const result = expr.analyze();
|
|
284
|
+
return [key, result];
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
const errorResult = {
|
|
288
|
+
valid: false,
|
|
289
|
+
celExpression: null,
|
|
290
|
+
dependencies: [],
|
|
291
|
+
sourceMap: [],
|
|
292
|
+
errors: [new ConversionError(error instanceof Error ? error.message : String(error), String(expr.originalExpression), 'unknown')],
|
|
293
|
+
warnings: [],
|
|
294
|
+
requiresConversion: true
|
|
295
|
+
};
|
|
296
|
+
return [key, errorResult];
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
const results = await Promise.all(promises);
|
|
300
|
+
return new Map(results);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Get statistics about the collection
|
|
304
|
+
*/
|
|
305
|
+
getStats() {
|
|
306
|
+
const total = this.size;
|
|
307
|
+
const requiresConversion = this.requiresConversionCount;
|
|
308
|
+
const static_ = this.staticCount;
|
|
309
|
+
const analyzed = this.analyzedCount;
|
|
310
|
+
return {
|
|
311
|
+
total,
|
|
312
|
+
requiresConversion,
|
|
313
|
+
static: static_,
|
|
314
|
+
analyzed,
|
|
315
|
+
pending: requiresConversion - analyzed,
|
|
316
|
+
conversionRatio: total > 0 ? requiresConversion / total : 0,
|
|
317
|
+
analysisProgress: requiresConversion > 0 ? analyzed / requiresConversion : 1
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Clear all expressions
|
|
322
|
+
*/
|
|
323
|
+
clear() {
|
|
324
|
+
this._expressions.clear();
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Reset analysis state for all expressions
|
|
328
|
+
*/
|
|
329
|
+
resetAll() {
|
|
330
|
+
for (const expr of this._expressions.values()) {
|
|
331
|
+
expr.reset();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Factory function to create lazy analyzed expressions
|
|
337
|
+
*/
|
|
338
|
+
export function createLazyExpression(expression, context, analyzer) {
|
|
339
|
+
return new LazyAnalyzedExpression(expression, context, analyzer);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Factory function to create lazy expression collections
|
|
343
|
+
*/
|
|
344
|
+
export function createLazyCollection(analyzer) {
|
|
345
|
+
return new LazyExpressionCollection(analyzer);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* On-demand expression analyzer that performs KubernetesRef detection and analysis
|
|
349
|
+
* only when results are actually needed
|
|
350
|
+
*/
|
|
351
|
+
export class OnDemandExpressionAnalyzer {
|
|
352
|
+
_analyzer;
|
|
353
|
+
_cache = new Map();
|
|
354
|
+
_cacheHits = 0;
|
|
355
|
+
_cacheMisses = 0;
|
|
356
|
+
constructor(analyzer) {
|
|
357
|
+
this._analyzer = analyzer || new JavaScriptToCelAnalyzer();
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Create a lazy expression that will be analyzed on-demand
|
|
361
|
+
*/
|
|
362
|
+
createLazyExpression(expression, context, cacheKey) {
|
|
363
|
+
// Generate cache key if not provided
|
|
364
|
+
const key = cacheKey || this._generateCacheKey(expression, context);
|
|
365
|
+
// Check cache first
|
|
366
|
+
const cached = this._cache.get(key);
|
|
367
|
+
if (cached) {
|
|
368
|
+
this._cacheHits++;
|
|
369
|
+
return cached;
|
|
370
|
+
}
|
|
371
|
+
// Create new lazy expression
|
|
372
|
+
this._cacheMisses++;
|
|
373
|
+
const lazy = new LazyAnalyzedExpression(expression, context, this._analyzer);
|
|
374
|
+
// Cache it for future use
|
|
375
|
+
this._cache.set(key, lazy);
|
|
376
|
+
return lazy;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Analyze an expression immediately if it contains KubernetesRef objects,
|
|
380
|
+
* otherwise return it as-is
|
|
381
|
+
*/
|
|
382
|
+
analyzeIfNeeded(expression, context) {
|
|
383
|
+
// Fast check for KubernetesRef objects
|
|
384
|
+
if (!containsKubernetesRefs(expression)) {
|
|
385
|
+
return {
|
|
386
|
+
needsConversion: false,
|
|
387
|
+
result: expression
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
// Create lazy expression for on-demand analysis
|
|
391
|
+
const lazy = this.createLazyExpression(expression, context);
|
|
392
|
+
return {
|
|
393
|
+
needsConversion: true,
|
|
394
|
+
result: lazy,
|
|
395
|
+
lazy
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Batch analyze multiple expressions, only processing those that need conversion
|
|
400
|
+
*/
|
|
401
|
+
batchAnalyze(expressions) {
|
|
402
|
+
const results = new Map();
|
|
403
|
+
for (const { key, expression, context } of expressions) {
|
|
404
|
+
const result = this.analyzeIfNeeded(expression, context);
|
|
405
|
+
results.set(key, result);
|
|
406
|
+
}
|
|
407
|
+
return results;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Get cache statistics
|
|
411
|
+
*/
|
|
412
|
+
getCacheStats() {
|
|
413
|
+
const total = this._cacheHits + this._cacheMisses;
|
|
414
|
+
return {
|
|
415
|
+
hits: this._cacheHits,
|
|
416
|
+
misses: this._cacheMisses,
|
|
417
|
+
size: this._cache.size,
|
|
418
|
+
hitRatio: total > 0 ? this._cacheHits / total : 0
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Clear the cache
|
|
423
|
+
*/
|
|
424
|
+
clearCache() {
|
|
425
|
+
this._cache.clear();
|
|
426
|
+
this._cacheHits = 0;
|
|
427
|
+
this._cacheMisses = 0;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Generate a cache key for an expression and context
|
|
431
|
+
*/
|
|
432
|
+
_generateCacheKey(expression, context) {
|
|
433
|
+
const exprStr = typeof expression === 'string'
|
|
434
|
+
? expression
|
|
435
|
+
: JSON.stringify(expression);
|
|
436
|
+
const contextStr = JSON.stringify({
|
|
437
|
+
type: context.type,
|
|
438
|
+
factoryType: context.factoryType,
|
|
439
|
+
availableRefs: Object.keys(context.availableReferences || {}),
|
|
440
|
+
strictTypeChecking: context.strictTypeChecking,
|
|
441
|
+
validateResourceReferences: context.validateResourceReferences
|
|
442
|
+
});
|
|
443
|
+
return `${exprStr}:${contextStr}`;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Expression tree analyzer for complex nested expressions with KubernetesRef objects
|
|
448
|
+
*/
|
|
449
|
+
export class ExpressionTreeAnalyzer {
|
|
450
|
+
_onDemandAnalyzer;
|
|
451
|
+
constructor(analyzer) {
|
|
452
|
+
this._onDemandAnalyzer = new OnDemandExpressionAnalyzer(analyzer);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Analyze a complex expression tree, creating lazy expressions for branches
|
|
456
|
+
* that contain KubernetesRef objects
|
|
457
|
+
*/
|
|
458
|
+
analyzeTree(expressionTree, context, path = []) {
|
|
459
|
+
const result = {
|
|
460
|
+
path,
|
|
461
|
+
needsConversion: false,
|
|
462
|
+
staticValue: null,
|
|
463
|
+
lazyExpression: null,
|
|
464
|
+
children: new Map()
|
|
465
|
+
};
|
|
466
|
+
// Handle primitive values
|
|
467
|
+
if (this._isPrimitive(expressionTree)) {
|
|
468
|
+
if (containsKubernetesRefs(expressionTree)) {
|
|
469
|
+
result.needsConversion = true;
|
|
470
|
+
result.lazyExpression = this._onDemandAnalyzer.createLazyExpression(expressionTree, context, path.join('.'));
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
result.staticValue = expressionTree;
|
|
474
|
+
}
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
// Handle arrays
|
|
478
|
+
if (Array.isArray(expressionTree)) {
|
|
479
|
+
let hasKubernetesRefs = false;
|
|
480
|
+
for (let i = 0; i < expressionTree.length; i++) {
|
|
481
|
+
const childPath = [...path, i.toString()];
|
|
482
|
+
const childResult = this.analyzeTree(expressionTree[i], context, childPath);
|
|
483
|
+
result.children.set(i.toString(), childResult);
|
|
484
|
+
if (childResult.needsConversion) {
|
|
485
|
+
hasKubernetesRefs = true;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (hasKubernetesRefs) {
|
|
489
|
+
result.needsConversion = true;
|
|
490
|
+
result.lazyExpression = this._onDemandAnalyzer.createLazyExpression(expressionTree, context, path.join('.'));
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
result.staticValue = expressionTree;
|
|
494
|
+
}
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
// Handle objects
|
|
498
|
+
if (expressionTree && typeof expressionTree === 'object') {
|
|
499
|
+
// First check if the object itself contains KubernetesRef objects
|
|
500
|
+
const objectHasRefs = containsKubernetesRefs(expressionTree);
|
|
501
|
+
let hasKubernetesRefs = objectHasRefs;
|
|
502
|
+
for (const [key, value] of Object.entries(expressionTree)) {
|
|
503
|
+
const childPath = [...path, key];
|
|
504
|
+
const childResult = this.analyzeTree(value, context, childPath);
|
|
505
|
+
result.children.set(key, childResult);
|
|
506
|
+
if (childResult.needsConversion) {
|
|
507
|
+
hasKubernetesRefs = true;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (hasKubernetesRefs) {
|
|
511
|
+
result.needsConversion = true;
|
|
512
|
+
result.lazyExpression = this._onDemandAnalyzer.createLazyExpression(expressionTree, context, path.join('.'));
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
result.staticValue = expressionTree;
|
|
516
|
+
}
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
519
|
+
// Fallback for unknown types
|
|
520
|
+
result.staticValue = expressionTree;
|
|
521
|
+
return result;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Get all lazy expressions from a tree result
|
|
525
|
+
*/
|
|
526
|
+
getAllLazyExpressions(treeResult) {
|
|
527
|
+
const expressions = [];
|
|
528
|
+
if (treeResult.lazyExpression) {
|
|
529
|
+
expressions.push(treeResult.lazyExpression);
|
|
530
|
+
}
|
|
531
|
+
for (const child of treeResult.children.values()) {
|
|
532
|
+
expressions.push(...this.getAllLazyExpressions(child));
|
|
533
|
+
}
|
|
534
|
+
return expressions;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Get statistics about the expression tree
|
|
538
|
+
*/
|
|
539
|
+
getTreeStats(treeResult) {
|
|
540
|
+
let totalNodes = 1;
|
|
541
|
+
let lazyNodes = treeResult.lazyExpression ? 1 : 0;
|
|
542
|
+
let staticNodes = treeResult.staticValue !== null ? 1 : 0;
|
|
543
|
+
let maxDepth = 0;
|
|
544
|
+
for (const child of treeResult.children.values()) {
|
|
545
|
+
const childStats = this.getTreeStats(child);
|
|
546
|
+
totalNodes += childStats.totalNodes;
|
|
547
|
+
lazyNodes += childStats.lazyNodes;
|
|
548
|
+
staticNodes += childStats.staticNodes;
|
|
549
|
+
maxDepth = Math.max(maxDepth, childStats.maxDepth + 1);
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
totalNodes,
|
|
553
|
+
lazyNodes,
|
|
554
|
+
staticNodes,
|
|
555
|
+
maxDepth,
|
|
556
|
+
lazyRatio: totalNodes > 0 ? lazyNodes / totalNodes : 0
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Check if a value is a primitive type
|
|
561
|
+
*/
|
|
562
|
+
_isPrimitive(value) {
|
|
563
|
+
return value === null ||
|
|
564
|
+
value === undefined ||
|
|
565
|
+
typeof value === 'string' ||
|
|
566
|
+
typeof value === 'number' ||
|
|
567
|
+
typeof value === 'boolean' ||
|
|
568
|
+
typeof value === 'function';
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Global on-demand analyzer instance
|
|
573
|
+
*/
|
|
574
|
+
export const globalOnDemandAnalyzer = new OnDemandExpressionAnalyzer();
|
|
575
|
+
/**
|
|
576
|
+
* Lazy loading manager for complex expression trees with magic proxy integration
|
|
577
|
+
*/
|
|
578
|
+
export class LazyExpressionTreeLoader {
|
|
579
|
+
_treeAnalyzer;
|
|
580
|
+
_loadedTrees = new Map();
|
|
581
|
+
_loadingPromises = new Map();
|
|
582
|
+
_loadCount = 0;
|
|
583
|
+
constructor(analyzer) {
|
|
584
|
+
this._treeAnalyzer = new ExpressionTreeAnalyzer(analyzer);
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Load an expression tree lazily, returning immediately with a promise
|
|
588
|
+
*/
|
|
589
|
+
async loadTree(expressionTree, context, treeId) {
|
|
590
|
+
const id = treeId || this._generateTreeId(expressionTree);
|
|
591
|
+
// Check if already loaded
|
|
592
|
+
const cached = this._loadedTrees.get(id);
|
|
593
|
+
if (cached) {
|
|
594
|
+
return cached;
|
|
595
|
+
}
|
|
596
|
+
// Check if currently loading
|
|
597
|
+
const loading = this._loadingPromises.get(id);
|
|
598
|
+
if (loading) {
|
|
599
|
+
return loading;
|
|
600
|
+
}
|
|
601
|
+
// Start loading
|
|
602
|
+
const loadPromise = this._performTreeLoad(expressionTree, context, id);
|
|
603
|
+
this._loadingPromises.set(id, loadPromise);
|
|
604
|
+
try {
|
|
605
|
+
const result = await loadPromise;
|
|
606
|
+
this._loadedTrees.set(id, result);
|
|
607
|
+
return result;
|
|
608
|
+
}
|
|
609
|
+
finally {
|
|
610
|
+
this._loadingPromises.delete(id);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Load multiple trees in parallel
|
|
615
|
+
*/
|
|
616
|
+
async loadTrees(trees) {
|
|
617
|
+
const loadPromises = trees.map(async ({ tree, context, id }) => {
|
|
618
|
+
const treeId = id || this._generateTreeId(tree);
|
|
619
|
+
const result = await this.loadTree(tree, context, treeId);
|
|
620
|
+
return [treeId, result];
|
|
621
|
+
});
|
|
622
|
+
const results = await Promise.all(loadPromises);
|
|
623
|
+
return new Map(results);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Get a tree if it's already loaded, otherwise return null
|
|
627
|
+
*/
|
|
628
|
+
getLoadedTree(treeId) {
|
|
629
|
+
return this._loadedTrees.get(treeId) || null;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Check if a tree is currently being loaded
|
|
633
|
+
*/
|
|
634
|
+
isLoading(treeId) {
|
|
635
|
+
return this._loadingPromises.has(treeId);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Check if a tree is loaded
|
|
639
|
+
*/
|
|
640
|
+
isLoaded(treeId) {
|
|
641
|
+
return this._loadedTrees.has(treeId);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Preload trees that are likely to be needed soon
|
|
645
|
+
*/
|
|
646
|
+
async preloadTrees(trees) {
|
|
647
|
+
// Sort by priority (higher priority first)
|
|
648
|
+
const sortedTrees = trees.sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
649
|
+
// Load high priority trees first
|
|
650
|
+
const highPriorityTrees = sortedTrees.filter(t => (t.priority || 0) > 5);
|
|
651
|
+
if (highPriorityTrees.length > 0) {
|
|
652
|
+
await this.loadTrees(highPriorityTrees);
|
|
653
|
+
}
|
|
654
|
+
// Load remaining trees in background
|
|
655
|
+
const remainingTrees = sortedTrees.filter(t => (t.priority || 0) <= 5);
|
|
656
|
+
if (remainingTrees.length > 0) {
|
|
657
|
+
// Don't await - load in background
|
|
658
|
+
this.loadTrees(remainingTrees).catch(error => {
|
|
659
|
+
console.warn('Background tree preloading failed:', error);
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Get loading statistics
|
|
665
|
+
*/
|
|
666
|
+
getStats() {
|
|
667
|
+
return {
|
|
668
|
+
loadedTrees: this._loadedTrees.size,
|
|
669
|
+
loadingTrees: this._loadingPromises.size,
|
|
670
|
+
totalLoads: this._loadCount,
|
|
671
|
+
memoryUsage: this._estimateMemoryUsage()
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Clear loaded trees to free memory
|
|
676
|
+
*/
|
|
677
|
+
clearCache() {
|
|
678
|
+
this._loadedTrees.clear();
|
|
679
|
+
this._loadCount = 0;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Perform the actual tree loading
|
|
683
|
+
*/
|
|
684
|
+
async _performTreeLoad(expressionTree, context, treeId) {
|
|
685
|
+
this._loadCount++;
|
|
686
|
+
// Use setTimeout to yield control and allow other operations
|
|
687
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
688
|
+
// Analyze the tree
|
|
689
|
+
const result = this._treeAnalyzer.analyzeTree(expressionTree, context, [treeId]);
|
|
690
|
+
return result;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Generate a unique ID for a tree
|
|
694
|
+
*/
|
|
695
|
+
_generateTreeId(tree) {
|
|
696
|
+
const treeStr = JSON.stringify(tree);
|
|
697
|
+
// Simple hash function for tree ID
|
|
698
|
+
let hash = 0;
|
|
699
|
+
for (let i = 0; i < treeStr.length; i++) {
|
|
700
|
+
const char = treeStr.charCodeAt(i);
|
|
701
|
+
hash = ((hash << 5) - hash) + char;
|
|
702
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
703
|
+
}
|
|
704
|
+
return `tree_${Math.abs(hash)}`;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Estimate memory usage of loaded trees
|
|
708
|
+
*/
|
|
709
|
+
_estimateMemoryUsage() {
|
|
710
|
+
let totalSize = 0;
|
|
711
|
+
for (const tree of this._loadedTrees.values()) {
|
|
712
|
+
totalSize += this._estimateTreeSize(tree);
|
|
713
|
+
}
|
|
714
|
+
return totalSize;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Estimate the size of a tree result
|
|
718
|
+
*/
|
|
719
|
+
_estimateTreeSize(tree) {
|
|
720
|
+
let size = 100; // Base size
|
|
721
|
+
if (tree.staticValue) {
|
|
722
|
+
size += JSON.stringify(tree.staticValue).length;
|
|
723
|
+
}
|
|
724
|
+
if (tree.lazyExpression) {
|
|
725
|
+
size += 200; // Estimated size of lazy expression
|
|
726
|
+
}
|
|
727
|
+
for (const child of tree.children.values()) {
|
|
728
|
+
size += this._estimateTreeSize(child);
|
|
729
|
+
}
|
|
730
|
+
return size;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Magic proxy integration for lazy expression loading
|
|
735
|
+
*/
|
|
736
|
+
export class MagicProxyLazyIntegration {
|
|
737
|
+
_treeLoader;
|
|
738
|
+
_proxyCache = new Map();
|
|
739
|
+
constructor(analyzer) {
|
|
740
|
+
this._treeLoader = new LazyExpressionTreeLoader(analyzer);
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Create a lazy proxy for a complex object that may contain KubernetesRef objects
|
|
744
|
+
*/
|
|
745
|
+
createLazyProxy(target, context, options = {}) {
|
|
746
|
+
const proxyId = options.id || this._generateProxyId(target);
|
|
747
|
+
// Check cache first
|
|
748
|
+
const cached = this._proxyCache.get(proxyId);
|
|
749
|
+
if (cached) {
|
|
750
|
+
return cached;
|
|
751
|
+
}
|
|
752
|
+
const proxy = new Proxy(target, {
|
|
753
|
+
get: (obj, prop) => {
|
|
754
|
+
const value = obj[prop];
|
|
755
|
+
// If the value contains KubernetesRef objects, wrap it in lazy analysis
|
|
756
|
+
if (containsKubernetesRefs(value)) {
|
|
757
|
+
return this._createLazyValue(value, context, `${proxyId}.${String(prop)}`);
|
|
758
|
+
}
|
|
759
|
+
// For complex objects, create nested lazy proxies
|
|
760
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
761
|
+
return this.createLazyProxy(value, context, {
|
|
762
|
+
...options,
|
|
763
|
+
id: `${proxyId}.${String(prop)}`
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
return value;
|
|
767
|
+
},
|
|
768
|
+
set: (obj, prop, value) => {
|
|
769
|
+
// Invalidate cache when properties are set
|
|
770
|
+
this._invalidateCache(proxyId);
|
|
771
|
+
obj[prop] = value;
|
|
772
|
+
return true;
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
// Cache the proxy
|
|
776
|
+
this._proxyCache.set(proxyId, proxy);
|
|
777
|
+
return proxy;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Load expression trees for all KubernetesRef-containing values in an object
|
|
781
|
+
*/
|
|
782
|
+
async preloadObjectTrees(obj, context, maxDepth = 5) {
|
|
783
|
+
const trees = [];
|
|
784
|
+
this._collectTreesFromObject(obj, context, trees, [], maxDepth);
|
|
785
|
+
if (trees.length > 0) {
|
|
786
|
+
await this._treeLoader.preloadTrees(trees);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Get statistics about the magic proxy integration
|
|
791
|
+
*/
|
|
792
|
+
getStats() {
|
|
793
|
+
return {
|
|
794
|
+
cachedProxies: this._proxyCache.size,
|
|
795
|
+
treeLoaderStats: this._treeLoader.getStats()
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Clear all caches
|
|
800
|
+
*/
|
|
801
|
+
clearCaches() {
|
|
802
|
+
this._proxyCache.clear();
|
|
803
|
+
this._treeLoader.clearCache();
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Create a lazy value wrapper
|
|
807
|
+
*/
|
|
808
|
+
_createLazyValue(value, context, valueId) {
|
|
809
|
+
// For simple values, create a lazy expression
|
|
810
|
+
if (this._isSimpleValue(value)) {
|
|
811
|
+
const lazy = globalOnDemandAnalyzer.createLazyExpression(value, context, valueId);
|
|
812
|
+
// Return a proxy that triggers analysis when accessed
|
|
813
|
+
return new Proxy({}, {
|
|
814
|
+
get: (_, prop) => {
|
|
815
|
+
if (prop === 'valueOf' || prop === 'toString') {
|
|
816
|
+
return () => lazy.result.celExpression?.expression || String(value);
|
|
817
|
+
}
|
|
818
|
+
if (prop === Symbol.toPrimitive) {
|
|
819
|
+
return () => lazy.result.celExpression?.expression || value;
|
|
820
|
+
}
|
|
821
|
+
// Trigger analysis and return the result
|
|
822
|
+
const result = lazy.result;
|
|
823
|
+
if (result.valid && result.celExpression) {
|
|
824
|
+
return result.celExpression.expression;
|
|
825
|
+
}
|
|
826
|
+
return value;
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
// For complex values, load the tree lazily
|
|
831
|
+
this._treeLoader.loadTree(value, context, valueId).catch(error => {
|
|
832
|
+
console.warn(`Failed to load tree for ${valueId}:`, error);
|
|
833
|
+
});
|
|
834
|
+
return value;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Check if a value is simple (not an object or array)
|
|
838
|
+
*/
|
|
839
|
+
_isSimpleValue(value) {
|
|
840
|
+
return value === null ||
|
|
841
|
+
value === undefined ||
|
|
842
|
+
typeof value === 'string' ||
|
|
843
|
+
typeof value === 'number' ||
|
|
844
|
+
typeof value === 'boolean' ||
|
|
845
|
+
typeof value === 'function';
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Generate a unique ID for a proxy
|
|
849
|
+
*/
|
|
850
|
+
_generateProxyId(target) {
|
|
851
|
+
const targetStr = JSON.stringify(target);
|
|
852
|
+
let hash = 0;
|
|
853
|
+
for (let i = 0; i < targetStr.length; i++) {
|
|
854
|
+
const char = targetStr.charCodeAt(i);
|
|
855
|
+
hash = ((hash << 5) - hash) + char;
|
|
856
|
+
hash = hash & hash;
|
|
857
|
+
}
|
|
858
|
+
return `proxy_${Math.abs(hash)}`;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Invalidate cache entries related to a proxy
|
|
862
|
+
*/
|
|
863
|
+
_invalidateCache(proxyId) {
|
|
864
|
+
// Remove the proxy from cache
|
|
865
|
+
this._proxyCache.delete(proxyId);
|
|
866
|
+
// Remove related entries (those that start with the proxy ID)
|
|
867
|
+
for (const key of this._proxyCache.keys()) {
|
|
868
|
+
if (key.startsWith(`${proxyId}.`)) {
|
|
869
|
+
this._proxyCache.delete(key);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Collect trees from an object recursively
|
|
875
|
+
*/
|
|
876
|
+
_collectTreesFromObject(obj, context, trees, path, maxDepth) {
|
|
877
|
+
if (maxDepth <= 0 || !obj || typeof obj !== 'object') {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (containsKubernetesRefs(obj)) {
|
|
881
|
+
trees.push({
|
|
882
|
+
tree: obj,
|
|
883
|
+
context,
|
|
884
|
+
id: path.join('.'),
|
|
885
|
+
priority: maxDepth // Higher depth = higher priority
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
// Recurse into object properties
|
|
889
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
890
|
+
this._collectTreesFromObject(value, context, trees, [...path, key], maxDepth - 1);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Global lazy tree loader instance
|
|
896
|
+
*/
|
|
897
|
+
export const globalLazyTreeLoader = new LazyExpressionTreeLoader();
|
|
898
|
+
/**
|
|
899
|
+
* Memory-optimized expression manager for large sets of KubernetesRef-containing expressions
|
|
900
|
+
*/
|
|
901
|
+
export class MemoryOptimizedExpressionManager {
|
|
902
|
+
_expressions = new Map();
|
|
903
|
+
_expressionSizes = new Map();
|
|
904
|
+
_accessTimes = new Map();
|
|
905
|
+
_analyzer;
|
|
906
|
+
_totalMemoryUsage = 0;
|
|
907
|
+
_maxMemoryUsage;
|
|
908
|
+
_cleanupThreshold;
|
|
909
|
+
_lastCleanup = Date.now();
|
|
910
|
+
constructor(analyzer, options = {}) {
|
|
911
|
+
this._analyzer = analyzer || new JavaScriptToCelAnalyzer();
|
|
912
|
+
this._maxMemoryUsage = options.maxMemoryUsage || 50 * 1024 * 1024; // 50MB default
|
|
913
|
+
this._cleanupThreshold = options.cleanupThreshold || 0.8; // Cleanup at 80% capacity
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Create or retrieve a lazy expression with memory management
|
|
917
|
+
*/
|
|
918
|
+
getOrCreateExpression(key, expression, context) {
|
|
919
|
+
// Check if expression exists and is still alive
|
|
920
|
+
const existingRef = this._expressions.get(key);
|
|
921
|
+
if (existingRef) {
|
|
922
|
+
const existing = existingRef.deref();
|
|
923
|
+
if (existing) {
|
|
924
|
+
this._accessTimes.set(key, Date.now());
|
|
925
|
+
return existing;
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
// Expression was garbage collected, clean up references
|
|
929
|
+
this._cleanupExpression(key);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
// Check if we need to cleanup before creating new expression
|
|
933
|
+
if (this._shouldCleanup()) {
|
|
934
|
+
this._performCleanup();
|
|
935
|
+
}
|
|
936
|
+
// Create new lazy expression
|
|
937
|
+
const lazy = new LazyAnalyzedExpression(expression, context, this._analyzer);
|
|
938
|
+
const estimatedSize = this._estimateExpressionSize(expression);
|
|
939
|
+
// Store with weak reference
|
|
940
|
+
this._expressions.set(key, new WeakRef(lazy));
|
|
941
|
+
this._expressionSizes.set(key, estimatedSize);
|
|
942
|
+
this._accessTimes.set(key, Date.now());
|
|
943
|
+
this._totalMemoryUsage += estimatedSize;
|
|
944
|
+
return lazy;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Batch create expressions with memory optimization
|
|
948
|
+
*/
|
|
949
|
+
batchCreateExpressions(expressions) {
|
|
950
|
+
const results = new Map();
|
|
951
|
+
// Sort by estimated size (smallest first) to optimize memory allocation
|
|
952
|
+
const sortedExpressions = expressions
|
|
953
|
+
.map(expr => ({
|
|
954
|
+
...expr,
|
|
955
|
+
estimatedSize: this._estimateExpressionSize(expr.expression)
|
|
956
|
+
}))
|
|
957
|
+
.sort((a, b) => a.estimatedSize - b.estimatedSize);
|
|
958
|
+
for (const { key, expression, context } of sortedExpressions) {
|
|
959
|
+
const lazy = this.getOrCreateExpression(key, expression, context);
|
|
960
|
+
results.set(key, lazy);
|
|
961
|
+
}
|
|
962
|
+
return results;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Get memory usage statistics
|
|
966
|
+
*/
|
|
967
|
+
getMemoryStats() {
|
|
968
|
+
// Clean up dead references first
|
|
969
|
+
this._cleanupDeadReferences();
|
|
970
|
+
return {
|
|
971
|
+
totalExpressions: this._expressions.size,
|
|
972
|
+
totalMemoryUsage: this._totalMemoryUsage,
|
|
973
|
+
maxMemoryUsage: this._maxMemoryUsage,
|
|
974
|
+
memoryUtilization: this._totalMemoryUsage / this._maxMemoryUsage,
|
|
975
|
+
averageExpressionSize: this._expressions.size > 0
|
|
976
|
+
? this._totalMemoryUsage / this._expressions.size
|
|
977
|
+
: 0,
|
|
978
|
+
lastCleanup: this._lastCleanup,
|
|
979
|
+
needsCleanup: this._shouldCleanup()
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Force cleanup of unused expressions
|
|
984
|
+
*/
|
|
985
|
+
forceCleanup() {
|
|
986
|
+
return this._performCleanup();
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Set memory limits
|
|
990
|
+
*/
|
|
991
|
+
setMemoryLimits(maxMemoryUsage, cleanupThreshold) {
|
|
992
|
+
this._maxMemoryUsage = maxMemoryUsage;
|
|
993
|
+
this._cleanupThreshold = cleanupThreshold;
|
|
994
|
+
// Trigger cleanup if we're now over the limit
|
|
995
|
+
if (this._shouldCleanup()) {
|
|
996
|
+
this._performCleanup();
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Clear all expressions
|
|
1001
|
+
*/
|
|
1002
|
+
clear() {
|
|
1003
|
+
this._expressions.clear();
|
|
1004
|
+
this._expressionSizes.clear();
|
|
1005
|
+
this._accessTimes.clear();
|
|
1006
|
+
this._totalMemoryUsage = 0;
|
|
1007
|
+
this._lastCleanup = Date.now();
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Get expressions that are likely to be garbage collected soon
|
|
1011
|
+
*/
|
|
1012
|
+
getExpiringExpressions() {
|
|
1013
|
+
const now = Date.now();
|
|
1014
|
+
const expiring = [];
|
|
1015
|
+
for (const [key, ref] of this._expressions) {
|
|
1016
|
+
const expr = ref.deref();
|
|
1017
|
+
if (!expr) {
|
|
1018
|
+
expiring.push(key);
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
const lastAccess = this._accessTimes.get(key) || 0;
|
|
1022
|
+
const age = now - lastAccess;
|
|
1023
|
+
// Consider expressions older than 5 minutes as expiring
|
|
1024
|
+
if (age > 5 * 60 * 1000) {
|
|
1025
|
+
expiring.push(key);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return expiring;
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Estimate the memory size of an expression
|
|
1033
|
+
*/
|
|
1034
|
+
_estimateExpressionSize(expression) {
|
|
1035
|
+
if (typeof expression === 'string') {
|
|
1036
|
+
return expression.length * 2; // UTF-16 encoding
|
|
1037
|
+
}
|
|
1038
|
+
if (typeof expression === 'function') {
|
|
1039
|
+
return expression.toString().length * 2 + 1000; // Function overhead
|
|
1040
|
+
}
|
|
1041
|
+
if (Array.isArray(expression)) {
|
|
1042
|
+
return expression.reduce((size, item) => size + this._estimateExpressionSize(item), 100);
|
|
1043
|
+
}
|
|
1044
|
+
if (expression && typeof expression === 'object') {
|
|
1045
|
+
let size = 100; // Object overhead
|
|
1046
|
+
for (const [key, value] of Object.entries(expression)) {
|
|
1047
|
+
size += key.length * 2; // Key size
|
|
1048
|
+
size += this._estimateExpressionSize(value); // Value size
|
|
1049
|
+
}
|
|
1050
|
+
return size;
|
|
1051
|
+
}
|
|
1052
|
+
return 50; // Default size for primitives
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Check if cleanup is needed
|
|
1056
|
+
*/
|
|
1057
|
+
_shouldCleanup() {
|
|
1058
|
+
return this._totalMemoryUsage > (this._maxMemoryUsage * this._cleanupThreshold);
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Perform memory cleanup
|
|
1062
|
+
*/
|
|
1063
|
+
_performCleanup() {
|
|
1064
|
+
const startTime = Date.now();
|
|
1065
|
+
const initialMemory = this._totalMemoryUsage;
|
|
1066
|
+
const initialCount = this._expressions.size;
|
|
1067
|
+
// First, clean up dead references
|
|
1068
|
+
const deadRefs = this._cleanupDeadReferences();
|
|
1069
|
+
// If still over threshold, remove least recently used expressions
|
|
1070
|
+
if (this._shouldCleanup()) {
|
|
1071
|
+
const lruCleanup = this._cleanupLRU();
|
|
1072
|
+
deadRefs.cleaned += lruCleanup.cleaned;
|
|
1073
|
+
deadRefs.freedMemory += lruCleanup.freedMemory;
|
|
1074
|
+
}
|
|
1075
|
+
this._lastCleanup = Date.now();
|
|
1076
|
+
return {
|
|
1077
|
+
duration: Date.now() - startTime,
|
|
1078
|
+
initialMemoryUsage: initialMemory,
|
|
1079
|
+
finalMemoryUsage: this._totalMemoryUsage,
|
|
1080
|
+
freedMemory: initialMemory - this._totalMemoryUsage,
|
|
1081
|
+
initialExpressionCount: initialCount,
|
|
1082
|
+
finalExpressionCount: this._expressions.size,
|
|
1083
|
+
cleanedExpressions: initialCount - this._expressions.size
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Clean up dead weak references
|
|
1088
|
+
*/
|
|
1089
|
+
_cleanupDeadReferences() {
|
|
1090
|
+
let cleaned = 0;
|
|
1091
|
+
let freedMemory = 0;
|
|
1092
|
+
for (const [key, ref] of this._expressions) {
|
|
1093
|
+
if (!ref.deref()) {
|
|
1094
|
+
const size = this._expressionSizes.get(key) || 0;
|
|
1095
|
+
this._cleanupExpression(key);
|
|
1096
|
+
cleaned++;
|
|
1097
|
+
freedMemory += size;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return { cleaned, freedMemory };
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Clean up least recently used expressions
|
|
1104
|
+
*/
|
|
1105
|
+
_cleanupLRU() {
|
|
1106
|
+
const _now = Date.now();
|
|
1107
|
+
const entries = Array.from(this._accessTimes.entries())
|
|
1108
|
+
.sort((a, b) => a[1] - b[1]); // Sort by access time (oldest first)
|
|
1109
|
+
let cleaned = 0;
|
|
1110
|
+
let freedMemory = 0;
|
|
1111
|
+
const targetMemory = this._maxMemoryUsage * (this._cleanupThreshold - 0.1); // Clean to 10% below threshold
|
|
1112
|
+
for (const [key] of entries) {
|
|
1113
|
+
if (this._totalMemoryUsage <= targetMemory) {
|
|
1114
|
+
break;
|
|
1115
|
+
}
|
|
1116
|
+
const size = this._expressionSizes.get(key) || 0;
|
|
1117
|
+
this._cleanupExpression(key);
|
|
1118
|
+
cleaned++;
|
|
1119
|
+
freedMemory += size;
|
|
1120
|
+
}
|
|
1121
|
+
return { cleaned, freedMemory };
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Clean up a single expression
|
|
1125
|
+
*/
|
|
1126
|
+
_cleanupExpression(key) {
|
|
1127
|
+
const size = this._expressionSizes.get(key) || 0;
|
|
1128
|
+
this._expressions.delete(key);
|
|
1129
|
+
this._expressionSizes.delete(key);
|
|
1130
|
+
this._accessTimes.delete(key);
|
|
1131
|
+
this._totalMemoryUsage -= size;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Advanced parallel expression analyzer for independent expressions with KubernetesRef objects
|
|
1136
|
+
*/
|
|
1137
|
+
export class ParallelExpressionAnalyzer {
|
|
1138
|
+
_analyzer;
|
|
1139
|
+
_maxConcurrency;
|
|
1140
|
+
_detector;
|
|
1141
|
+
_activeAnalyses = 0;
|
|
1142
|
+
_totalAnalyses = 0;
|
|
1143
|
+
_completedAnalyses = 0;
|
|
1144
|
+
_failedAnalyses = 0;
|
|
1145
|
+
constructor(analyzer, maxConcurrency = 4, detector) {
|
|
1146
|
+
this._analyzer = analyzer || new JavaScriptToCelAnalyzer();
|
|
1147
|
+
this._maxConcurrency = maxConcurrency;
|
|
1148
|
+
this._detector = detector || new OptimizedKubernetesRefDetector();
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Analyze multiple expressions in parallel with advanced scheduling
|
|
1152
|
+
*/
|
|
1153
|
+
async analyzeParallel(expressions) {
|
|
1154
|
+
const results = new Map();
|
|
1155
|
+
// Pre-filter expressions that need conversion
|
|
1156
|
+
const filteredExpressions = expressions.filter(({ expression }) => this._detector.containsKubernetesRefs(expression));
|
|
1157
|
+
// Add static expressions directly to results
|
|
1158
|
+
for (const { key, expression } of expressions) {
|
|
1159
|
+
if (!this._detector.containsKubernetesRefs(expression)) {
|
|
1160
|
+
results.set(key, {
|
|
1161
|
+
valid: true,
|
|
1162
|
+
celExpression: null,
|
|
1163
|
+
dependencies: [],
|
|
1164
|
+
sourceMap: [],
|
|
1165
|
+
errors: [],
|
|
1166
|
+
warnings: [],
|
|
1167
|
+
requiresConversion: false
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
if (filteredExpressions.length === 0) {
|
|
1172
|
+
return results;
|
|
1173
|
+
}
|
|
1174
|
+
this._totalAnalyses = filteredExpressions.length;
|
|
1175
|
+
this._completedAnalyses = 0;
|
|
1176
|
+
this._failedAnalyses = 0;
|
|
1177
|
+
// Create work queue with dependency analysis
|
|
1178
|
+
const workQueue = await this._createWorkQueue(filteredExpressions);
|
|
1179
|
+
// Process work queue in parallel
|
|
1180
|
+
const parallelResults = await this._processWorkQueue(workQueue);
|
|
1181
|
+
// Merge results
|
|
1182
|
+
for (const [key, result] of parallelResults) {
|
|
1183
|
+
results.set(key, result);
|
|
1184
|
+
}
|
|
1185
|
+
return results;
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Analyze expressions with priority-based scheduling and dependency resolution
|
|
1189
|
+
*/
|
|
1190
|
+
async analyzePrioritized(expressions) {
|
|
1191
|
+
// Build dependency graph
|
|
1192
|
+
const dependencyGraph = this._buildDependencyGraph(expressions);
|
|
1193
|
+
// Topological sort to respect dependencies
|
|
1194
|
+
const sortedExpressions = this._topologicalSort(expressions, dependencyGraph);
|
|
1195
|
+
// Group by priority within dependency levels
|
|
1196
|
+
const priorityGroups = this._groupByPriority(sortedExpressions);
|
|
1197
|
+
const results = new Map();
|
|
1198
|
+
// Process each priority group
|
|
1199
|
+
for (const group of priorityGroups) {
|
|
1200
|
+
const groupResults = await this.analyzeParallel(group);
|
|
1201
|
+
for (const [key, result] of groupResults) {
|
|
1202
|
+
results.set(key, result);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
return results;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Analyze expressions with adaptive concurrency based on system load
|
|
1209
|
+
*/
|
|
1210
|
+
async analyzeAdaptive(expressions, options = {}) {
|
|
1211
|
+
const { initialConcurrency = this._maxConcurrency, maxConcurrency = this._maxConcurrency * 2, minConcurrency = 1, performanceThreshold = 100 // ms
|
|
1212
|
+
} = options;
|
|
1213
|
+
let currentConcurrency = initialConcurrency;
|
|
1214
|
+
const results = new Map();
|
|
1215
|
+
const remaining = [...expressions];
|
|
1216
|
+
while (remaining.length > 0) {
|
|
1217
|
+
// Take batch based on current concurrency
|
|
1218
|
+
const batch = remaining.splice(0, currentConcurrency);
|
|
1219
|
+
// Measure performance
|
|
1220
|
+
const startTime = performance.now();
|
|
1221
|
+
const batchResults = await this._processBatch(batch);
|
|
1222
|
+
const endTime = performance.now();
|
|
1223
|
+
const avgTime = (endTime - startTime) / batch.length;
|
|
1224
|
+
// Adapt concurrency based on performance
|
|
1225
|
+
if (avgTime > performanceThreshold && currentConcurrency > minConcurrency) {
|
|
1226
|
+
currentConcurrency = Math.max(minConcurrency, currentConcurrency - 1);
|
|
1227
|
+
}
|
|
1228
|
+
else if (avgTime < performanceThreshold / 2 && currentConcurrency < maxConcurrency) {
|
|
1229
|
+
currentConcurrency = Math.min(maxConcurrency, currentConcurrency + 1);
|
|
1230
|
+
}
|
|
1231
|
+
// Merge results
|
|
1232
|
+
for (const [key, result] of batchResults) {
|
|
1233
|
+
results.set(key, result);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return results;
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Stream analysis results as they complete
|
|
1240
|
+
*/
|
|
1241
|
+
async *analyzeStream(expressions) {
|
|
1242
|
+
const total = expressions.length;
|
|
1243
|
+
let completed = 0;
|
|
1244
|
+
// Filter expressions that need conversion
|
|
1245
|
+
const needConversion = expressions.filter(({ expression }) => this._detector.containsKubernetesRefs(expression));
|
|
1246
|
+
// Yield static results immediately
|
|
1247
|
+
for (const { key, expression } of expressions) {
|
|
1248
|
+
if (!this._detector.containsKubernetesRefs(expression)) {
|
|
1249
|
+
completed++;
|
|
1250
|
+
yield {
|
|
1251
|
+
key,
|
|
1252
|
+
result: {
|
|
1253
|
+
valid: true,
|
|
1254
|
+
celExpression: null,
|
|
1255
|
+
dependencies: [],
|
|
1256
|
+
sourceMap: [],
|
|
1257
|
+
errors: [],
|
|
1258
|
+
warnings: [],
|
|
1259
|
+
requiresConversion: false
|
|
1260
|
+
},
|
|
1261
|
+
progress: completed / total
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
// Process expressions that need conversion
|
|
1266
|
+
const batches = this._createBatches(needConversion, this._maxConcurrency);
|
|
1267
|
+
for (const batch of batches) {
|
|
1268
|
+
const promises = batch.map(async ({ key, expression, context }) => {
|
|
1269
|
+
this._activeAnalyses++;
|
|
1270
|
+
try {
|
|
1271
|
+
const result = this._analyzer.analyzeExpressionWithRefs(expression, context);
|
|
1272
|
+
this._completedAnalyses++;
|
|
1273
|
+
return { key, result };
|
|
1274
|
+
}
|
|
1275
|
+
catch (error) {
|
|
1276
|
+
this._failedAnalyses++;
|
|
1277
|
+
const errorResult = {
|
|
1278
|
+
valid: false,
|
|
1279
|
+
celExpression: null,
|
|
1280
|
+
dependencies: [],
|
|
1281
|
+
sourceMap: [],
|
|
1282
|
+
errors: [new ConversionError(error instanceof Error ? error.message : String(error), String(expression), 'unknown')],
|
|
1283
|
+
warnings: [],
|
|
1284
|
+
requiresConversion: true
|
|
1285
|
+
};
|
|
1286
|
+
return { key, result: errorResult };
|
|
1287
|
+
}
|
|
1288
|
+
finally {
|
|
1289
|
+
this._activeAnalyses--;
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
// Yield results as they complete
|
|
1293
|
+
for (const promise of promises) {
|
|
1294
|
+
const { key, result } = await promise;
|
|
1295
|
+
completed++;
|
|
1296
|
+
yield {
|
|
1297
|
+
key,
|
|
1298
|
+
result,
|
|
1299
|
+
progress: completed / total
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Get comprehensive analysis statistics
|
|
1306
|
+
*/
|
|
1307
|
+
getStats() {
|
|
1308
|
+
return {
|
|
1309
|
+
maxConcurrency: this._maxConcurrency,
|
|
1310
|
+
activeAnalyses: this._activeAnalyses,
|
|
1311
|
+
utilizationRatio: this._activeAnalyses / this._maxConcurrency,
|
|
1312
|
+
totalAnalyses: this._totalAnalyses,
|
|
1313
|
+
completedAnalyses: this._completedAnalyses,
|
|
1314
|
+
failedAnalyses: this._failedAnalyses,
|
|
1315
|
+
successRate: this._totalAnalyses > 0 ? this._completedAnalyses / this._totalAnalyses : 0,
|
|
1316
|
+
detectorStats: this._detector.getCacheStats()
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Create work queue with complexity analysis
|
|
1321
|
+
*/
|
|
1322
|
+
async _createWorkQueue(expressions) {
|
|
1323
|
+
const workItems = [];
|
|
1324
|
+
for (const { key, expression, context } of expressions) {
|
|
1325
|
+
const complexity = this._calculateComplexity(expression);
|
|
1326
|
+
const refCount = this._detector.extractKubernetesRefs(expression).length;
|
|
1327
|
+
workItems.push({
|
|
1328
|
+
key,
|
|
1329
|
+
expression,
|
|
1330
|
+
context,
|
|
1331
|
+
complexity,
|
|
1332
|
+
refCount,
|
|
1333
|
+
estimatedTime: complexity * 10 + refCount * 5 // Simple estimation
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
// Sort by estimated time (shortest first for better parallelization)
|
|
1337
|
+
return workItems.sort((a, b) => a.estimatedTime - b.estimatedTime);
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Process work queue in parallel
|
|
1341
|
+
*/
|
|
1342
|
+
async _processWorkQueue(workQueue) {
|
|
1343
|
+
const results = new Map();
|
|
1344
|
+
const batches = this._createBatches(workQueue, this._maxConcurrency);
|
|
1345
|
+
for (const batch of batches) {
|
|
1346
|
+
const batchResults = await this._processBatch(batch);
|
|
1347
|
+
for (const [key, result] of batchResults) {
|
|
1348
|
+
results.set(key, result);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
return results;
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Process a batch of work items
|
|
1355
|
+
*/
|
|
1356
|
+
async _processBatch(batch) {
|
|
1357
|
+
const promises = batch.map(async ({ key, expression, context }) => {
|
|
1358
|
+
this._activeAnalyses++;
|
|
1359
|
+
try {
|
|
1360
|
+
const result = this._analyzer.analyzeExpressionWithRefs(expression, context);
|
|
1361
|
+
this._completedAnalyses++;
|
|
1362
|
+
return [key, result];
|
|
1363
|
+
}
|
|
1364
|
+
catch (error) {
|
|
1365
|
+
this._failedAnalyses++;
|
|
1366
|
+
const errorResult = {
|
|
1367
|
+
valid: false,
|
|
1368
|
+
celExpression: null,
|
|
1369
|
+
dependencies: [],
|
|
1370
|
+
sourceMap: [],
|
|
1371
|
+
errors: [new ConversionError(error instanceof Error ? error.message : String(error), String(expression), 'unknown')],
|
|
1372
|
+
warnings: [],
|
|
1373
|
+
requiresConversion: true
|
|
1374
|
+
};
|
|
1375
|
+
return [key, errorResult];
|
|
1376
|
+
}
|
|
1377
|
+
finally {
|
|
1378
|
+
this._activeAnalyses--;
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
const results = await Promise.all(promises);
|
|
1382
|
+
return new Map(results);
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Build dependency graph for expressions
|
|
1386
|
+
*/
|
|
1387
|
+
_buildDependencyGraph(expressions) {
|
|
1388
|
+
const graph = new Map();
|
|
1389
|
+
for (const { key, dependencies = [] } of expressions) {
|
|
1390
|
+
graph.set(key, dependencies);
|
|
1391
|
+
}
|
|
1392
|
+
return graph;
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Topological sort for dependency resolution
|
|
1396
|
+
*/
|
|
1397
|
+
_topologicalSort(expressions, dependencyGraph) {
|
|
1398
|
+
const visited = new Set();
|
|
1399
|
+
const visiting = new Set();
|
|
1400
|
+
const result = [];
|
|
1401
|
+
const expressionMap = new Map(expressions.map(expr => [expr.key, expr]));
|
|
1402
|
+
const visit = (key) => {
|
|
1403
|
+
if (visited.has(key))
|
|
1404
|
+
return;
|
|
1405
|
+
if (visiting.has(key)) {
|
|
1406
|
+
throw new Error(`Circular dependency detected involving ${key}`);
|
|
1407
|
+
}
|
|
1408
|
+
visiting.add(key);
|
|
1409
|
+
const dependencies = dependencyGraph.get(key) || [];
|
|
1410
|
+
for (const dep of dependencies) {
|
|
1411
|
+
visit(dep);
|
|
1412
|
+
}
|
|
1413
|
+
visiting.delete(key);
|
|
1414
|
+
visited.add(key);
|
|
1415
|
+
const expression = expressionMap.get(key);
|
|
1416
|
+
if (expression) {
|
|
1417
|
+
result.push(expression);
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
for (const { key } of expressions) {
|
|
1421
|
+
visit(key);
|
|
1422
|
+
}
|
|
1423
|
+
return result;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Group expressions by priority
|
|
1427
|
+
*/
|
|
1428
|
+
_groupByPriority(expressions) {
|
|
1429
|
+
const groups = new Map();
|
|
1430
|
+
for (const expr of expressions) {
|
|
1431
|
+
const priority = expr.priority;
|
|
1432
|
+
if (!groups.has(priority)) {
|
|
1433
|
+
groups.set(priority, []);
|
|
1434
|
+
}
|
|
1435
|
+
groups.get(priority)?.push(expr);
|
|
1436
|
+
}
|
|
1437
|
+
// Sort by priority (highest first)
|
|
1438
|
+
const sortedPriorities = Array.from(groups.keys()).sort((a, b) => b - a);
|
|
1439
|
+
return sortedPriorities.map(priority => groups.get(priority));
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Calculate expression complexity
|
|
1443
|
+
*/
|
|
1444
|
+
_calculateComplexity(expression) {
|
|
1445
|
+
if (typeof expression === 'string') {
|
|
1446
|
+
let complexity = Math.min(expression.length / 20, 10);
|
|
1447
|
+
if (expression.includes('?.'))
|
|
1448
|
+
complexity += 2;
|
|
1449
|
+
if (expression.includes('??'))
|
|
1450
|
+
complexity += 2;
|
|
1451
|
+
if (expression.includes('${'))
|
|
1452
|
+
complexity += 3;
|
|
1453
|
+
return complexity;
|
|
1454
|
+
}
|
|
1455
|
+
if (Array.isArray(expression)) {
|
|
1456
|
+
return expression.reduce((sum, item) => sum + this._calculateComplexity(item), 1);
|
|
1457
|
+
}
|
|
1458
|
+
if (expression && typeof expression === 'object') {
|
|
1459
|
+
let complexity = 1;
|
|
1460
|
+
for (const value of Object.values(expression)) {
|
|
1461
|
+
complexity += this._calculateComplexity(value);
|
|
1462
|
+
}
|
|
1463
|
+
return Math.min(complexity, 50); // Cap complexity
|
|
1464
|
+
}
|
|
1465
|
+
return 1;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Create batches for parallel processing
|
|
1469
|
+
*/
|
|
1470
|
+
_createBatches(items, batchSize) {
|
|
1471
|
+
const batches = [];
|
|
1472
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
1473
|
+
batches.push(items.slice(i, i + batchSize));
|
|
1474
|
+
}
|
|
1475
|
+
return batches;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Global memory-optimized expression manager
|
|
1480
|
+
*/
|
|
1481
|
+
export const globalMemoryOptimizedManager = new MemoryOptimizedExpressionManager();
|
|
1482
|
+
/**
|
|
1483
|
+
* Performance profiler for expression analysis with KubernetesRef detection
|
|
1484
|
+
*/
|
|
1485
|
+
export class ExpressionAnalysisProfiler {
|
|
1486
|
+
_profiles = new Map();
|
|
1487
|
+
_analyzer;
|
|
1488
|
+
_enabled = true;
|
|
1489
|
+
constructor(analyzer) {
|
|
1490
|
+
this._analyzer = analyzer || new JavaScriptToCelAnalyzer();
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Enable or disable profiling
|
|
1494
|
+
*/
|
|
1495
|
+
setEnabled(enabled) {
|
|
1496
|
+
this._enabled = enabled;
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Profile expression analysis performance
|
|
1500
|
+
*/
|
|
1501
|
+
profileExpression(expression, context, profileId) {
|
|
1502
|
+
const id = profileId || this._generateProfileId(expression);
|
|
1503
|
+
if (!this._enabled) {
|
|
1504
|
+
const result = this._analyzer.analyzeExpressionWithRefs(expression, context);
|
|
1505
|
+
return {
|
|
1506
|
+
result,
|
|
1507
|
+
profile: {
|
|
1508
|
+
id,
|
|
1509
|
+
expression: String(expression),
|
|
1510
|
+
startTime: Date.now(),
|
|
1511
|
+
endTime: Date.now(),
|
|
1512
|
+
duration: 0,
|
|
1513
|
+
kubernetesRefDetectionTime: 0,
|
|
1514
|
+
astParsingTime: 0,
|
|
1515
|
+
celGenerationTime: 0,
|
|
1516
|
+
memoryUsage: 0,
|
|
1517
|
+
kubernetesRefCount: 0,
|
|
1518
|
+
expressionComplexity: 0,
|
|
1519
|
+
cacheHit: false
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
const profile = {
|
|
1524
|
+
id,
|
|
1525
|
+
expression: String(expression),
|
|
1526
|
+
startTime: performance.now(),
|
|
1527
|
+
endTime: 0,
|
|
1528
|
+
duration: 0,
|
|
1529
|
+
kubernetesRefDetectionTime: 0,
|
|
1530
|
+
astParsingTime: 0,
|
|
1531
|
+
celGenerationTime: 0,
|
|
1532
|
+
memoryUsage: 0,
|
|
1533
|
+
kubernetesRefCount: 0,
|
|
1534
|
+
expressionComplexity: this._calculateComplexity(expression),
|
|
1535
|
+
cacheHit: false
|
|
1536
|
+
};
|
|
1537
|
+
// Profile KubernetesRef detection
|
|
1538
|
+
const refDetectionStart = performance.now();
|
|
1539
|
+
const hasRefs = containsKubernetesRefs(expression);
|
|
1540
|
+
const refDetectionEnd = performance.now();
|
|
1541
|
+
profile.kubernetesRefDetectionTime = refDetectionEnd - refDetectionStart;
|
|
1542
|
+
if (hasRefs) {
|
|
1543
|
+
const refs = extractResourceReferences(expression);
|
|
1544
|
+
profile.kubernetesRefCount = refs.length;
|
|
1545
|
+
}
|
|
1546
|
+
// Profile the actual analysis
|
|
1547
|
+
const analysisStart = performance.now();
|
|
1548
|
+
const result = this._analyzer.analyzeExpressionWithRefs(expression, context);
|
|
1549
|
+
const analysisEnd = performance.now();
|
|
1550
|
+
profile.endTime = performance.now();
|
|
1551
|
+
profile.duration = profile.endTime - profile.startTime;
|
|
1552
|
+
profile.celGenerationTime = analysisEnd - analysisStart - profile.kubernetesRefDetectionTime;
|
|
1553
|
+
profile.memoryUsage = this._estimateMemoryUsage(expression, result);
|
|
1554
|
+
// Store the profile
|
|
1555
|
+
this._profiles.set(id, profile);
|
|
1556
|
+
return { result, profile };
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Profile multiple expressions in batch
|
|
1560
|
+
*/
|
|
1561
|
+
profileBatch(expressions) {
|
|
1562
|
+
const results = new Map();
|
|
1563
|
+
for (const { expression, context, id } of expressions) {
|
|
1564
|
+
const profileResult = this.profileExpression(expression, context, id);
|
|
1565
|
+
const profileId = id || this._generateProfileId(expression);
|
|
1566
|
+
results.set(profileId, profileResult);
|
|
1567
|
+
}
|
|
1568
|
+
return results;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Get performance statistics
|
|
1572
|
+
*/
|
|
1573
|
+
getStats() {
|
|
1574
|
+
const profiles = Array.from(this._profiles.values());
|
|
1575
|
+
if (profiles.length === 0) {
|
|
1576
|
+
return {
|
|
1577
|
+
totalProfiles: 0,
|
|
1578
|
+
averageDuration: 0,
|
|
1579
|
+
averageKubernetesRefDetectionTime: 0,
|
|
1580
|
+
averageCelGenerationTime: 0,
|
|
1581
|
+
averageMemoryUsage: 0,
|
|
1582
|
+
averageKubernetesRefCount: 0,
|
|
1583
|
+
averageComplexity: 0,
|
|
1584
|
+
slowestExpression: null,
|
|
1585
|
+
fastestExpression: null,
|
|
1586
|
+
mostComplexExpression: null,
|
|
1587
|
+
cacheHitRatio: 0
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
const totalDuration = profiles.reduce((sum, p) => sum + p.duration, 0);
|
|
1591
|
+
const totalRefDetection = profiles.reduce((sum, p) => sum + p.kubernetesRefDetectionTime, 0);
|
|
1592
|
+
const totalCelGeneration = profiles.reduce((sum, p) => sum + p.celGenerationTime, 0);
|
|
1593
|
+
const totalMemory = profiles.reduce((sum, p) => sum + p.memoryUsage, 0);
|
|
1594
|
+
const totalRefCount = profiles.reduce((sum, p) => sum + p.kubernetesRefCount, 0);
|
|
1595
|
+
const totalComplexity = profiles.reduce((sum, p) => sum + p.expressionComplexity, 0);
|
|
1596
|
+
const cacheHits = profiles.filter(p => p.cacheHit).length;
|
|
1597
|
+
const sortedByDuration = [...profiles].sort((a, b) => b.duration - a.duration);
|
|
1598
|
+
const sortedByComplexity = [...profiles].sort((a, b) => b.expressionComplexity - a.expressionComplexity);
|
|
1599
|
+
return {
|
|
1600
|
+
totalProfiles: profiles.length,
|
|
1601
|
+
averageDuration: totalDuration / profiles.length,
|
|
1602
|
+
averageKubernetesRefDetectionTime: totalRefDetection / profiles.length,
|
|
1603
|
+
averageCelGenerationTime: totalCelGeneration / profiles.length,
|
|
1604
|
+
averageMemoryUsage: totalMemory / profiles.length,
|
|
1605
|
+
averageKubernetesRefCount: totalRefCount / profiles.length,
|
|
1606
|
+
averageComplexity: totalComplexity / profiles.length,
|
|
1607
|
+
slowestExpression: sortedByDuration[0] || null,
|
|
1608
|
+
fastestExpression: sortedByDuration[sortedByDuration.length - 1] || null,
|
|
1609
|
+
mostComplexExpression: sortedByComplexity[0] || null,
|
|
1610
|
+
cacheHitRatio: profiles.length > 0 ? cacheHits / profiles.length : 0
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Get profiles that exceed performance thresholds
|
|
1615
|
+
*/
|
|
1616
|
+
getSlowProfiles(durationThreshold = 10) {
|
|
1617
|
+
return Array.from(this._profiles.values())
|
|
1618
|
+
.filter(profile => profile.duration > durationThreshold)
|
|
1619
|
+
.sort((a, b) => b.duration - a.duration);
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Get profiles with high KubernetesRef detection overhead
|
|
1623
|
+
*/
|
|
1624
|
+
getHighOverheadProfiles(overheadThreshold = 0.5) {
|
|
1625
|
+
return Array.from(this._profiles.values())
|
|
1626
|
+
.filter(profile => {
|
|
1627
|
+
const overhead = profile.duration > 0
|
|
1628
|
+
? profile.kubernetesRefDetectionTime / profile.duration
|
|
1629
|
+
: 0;
|
|
1630
|
+
return overhead > overheadThreshold;
|
|
1631
|
+
})
|
|
1632
|
+
.sort((a, b) => {
|
|
1633
|
+
const aOverhead = a.duration > 0 ? a.kubernetesRefDetectionTime / a.duration : 0;
|
|
1634
|
+
const bOverhead = b.duration > 0 ? b.kubernetesRefDetectionTime / b.duration : 0;
|
|
1635
|
+
return bOverhead - aOverhead;
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Clear all profiles
|
|
1640
|
+
*/
|
|
1641
|
+
clearProfiles() {
|
|
1642
|
+
this._profiles.clear();
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Export profiles for analysis
|
|
1646
|
+
*/
|
|
1647
|
+
exportProfiles() {
|
|
1648
|
+
return Array.from(this._profiles.values());
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Generate a profile ID
|
|
1652
|
+
*/
|
|
1653
|
+
_generateProfileId(expression) {
|
|
1654
|
+
const exprStr = String(expression);
|
|
1655
|
+
const timestamp = Date.now();
|
|
1656
|
+
return `profile_${timestamp}_${exprStr.slice(0, 20).replace(/\W/g, '_')}`;
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Calculate expression complexity score
|
|
1660
|
+
*/
|
|
1661
|
+
_calculateComplexity(expression) {
|
|
1662
|
+
if (typeof expression === 'string') {
|
|
1663
|
+
// String complexity based on length and special characters
|
|
1664
|
+
let complexity = Math.min(expression.length / 10, 10); // Max 10 for length
|
|
1665
|
+
// Add complexity for special patterns
|
|
1666
|
+
if (expression.includes('?.'))
|
|
1667
|
+
complexity += 2; // Optional chaining
|
|
1668
|
+
if (expression.includes('??'))
|
|
1669
|
+
complexity += 2; // Nullish coalescing
|
|
1670
|
+
if (expression.includes('${'))
|
|
1671
|
+
complexity += 3; // Template literals
|
|
1672
|
+
if (expression.match(/\w+\.\w+/))
|
|
1673
|
+
complexity += 1; // Property access
|
|
1674
|
+
return complexity;
|
|
1675
|
+
}
|
|
1676
|
+
if (Array.isArray(expression)) {
|
|
1677
|
+
return expression.reduce((sum, item) => sum + this._calculateComplexity(item), 1);
|
|
1678
|
+
}
|
|
1679
|
+
if (expression && typeof expression === 'object') {
|
|
1680
|
+
let complexity = 1;
|
|
1681
|
+
for (const value of Object.values(expression)) {
|
|
1682
|
+
complexity += this._calculateComplexity(value);
|
|
1683
|
+
}
|
|
1684
|
+
return complexity;
|
|
1685
|
+
}
|
|
1686
|
+
return 1; // Base complexity for primitives
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Estimate memory usage
|
|
1690
|
+
*/
|
|
1691
|
+
_estimateMemoryUsage(expression, result) {
|
|
1692
|
+
let size = 0;
|
|
1693
|
+
// Expression size
|
|
1694
|
+
if (typeof expression === 'string') {
|
|
1695
|
+
size += expression.length * 2; // UTF-16
|
|
1696
|
+
}
|
|
1697
|
+
else {
|
|
1698
|
+
size += JSON.stringify(expression).length * 2;
|
|
1699
|
+
}
|
|
1700
|
+
// Result size
|
|
1701
|
+
if (result.celExpression) {
|
|
1702
|
+
size += result.celExpression.expression.length * 2;
|
|
1703
|
+
}
|
|
1704
|
+
// Dependencies size
|
|
1705
|
+
size += result.dependencies.length * 100; // Estimated size per dependency
|
|
1706
|
+
// Source map size
|
|
1707
|
+
size += result.sourceMap.length * 200; // Estimated size per source map entry
|
|
1708
|
+
return size;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Optimized KubernetesRef detector with caching and fast paths
|
|
1713
|
+
*/
|
|
1714
|
+
export class OptimizedKubernetesRefDetector {
|
|
1715
|
+
_cache = new Map();
|
|
1716
|
+
_refCache = new Map();
|
|
1717
|
+
_cacheHits = 0;
|
|
1718
|
+
_cacheMisses = 0;
|
|
1719
|
+
_maxCacheSize = 1000;
|
|
1720
|
+
/**
|
|
1721
|
+
* Fast detection of KubernetesRef objects with caching
|
|
1722
|
+
*/
|
|
1723
|
+
containsKubernetesRefs(value, useCache = true) {
|
|
1724
|
+
if (!useCache) {
|
|
1725
|
+
return this._containsKubernetesRefsUncached(value);
|
|
1726
|
+
}
|
|
1727
|
+
const cacheKey = this._generateCacheKey(value);
|
|
1728
|
+
// Check cache first
|
|
1729
|
+
const cached = this._cache.get(cacheKey);
|
|
1730
|
+
if (cached !== undefined) {
|
|
1731
|
+
this._cacheHits++;
|
|
1732
|
+
return cached;
|
|
1733
|
+
}
|
|
1734
|
+
// Compute result
|
|
1735
|
+
this._cacheMisses++;
|
|
1736
|
+
const result = this._containsKubernetesRefsUncached(value);
|
|
1737
|
+
// Cache result if cache isn't full
|
|
1738
|
+
if (this._cache.size < this._maxCacheSize) {
|
|
1739
|
+
this._cache.set(cacheKey, result);
|
|
1740
|
+
}
|
|
1741
|
+
return result;
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Extract KubernetesRef objects with optimized traversal
|
|
1745
|
+
*/
|
|
1746
|
+
extractKubernetesRefs(value, useCache = true) {
|
|
1747
|
+
if (!useCache) {
|
|
1748
|
+
return this._extractKubernetesRefsUncached(value);
|
|
1749
|
+
}
|
|
1750
|
+
const cacheKey = this._generateCacheKey(value);
|
|
1751
|
+
// Check cache first
|
|
1752
|
+
const cached = this._refCache.get(cacheKey);
|
|
1753
|
+
if (cached !== undefined) {
|
|
1754
|
+
this._cacheHits++;
|
|
1755
|
+
return [...cached]; // Return copy to prevent mutation
|
|
1756
|
+
}
|
|
1757
|
+
// Compute result
|
|
1758
|
+
this._cacheMisses++;
|
|
1759
|
+
const result = this._extractKubernetesRefsUncached(value);
|
|
1760
|
+
// Cache result if cache isn't full
|
|
1761
|
+
if (this._refCache.size < this._maxCacheSize) {
|
|
1762
|
+
this._refCache.set(cacheKey, [...result]); // Store copy
|
|
1763
|
+
}
|
|
1764
|
+
return result;
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Optimized traversal that stops early when possible
|
|
1768
|
+
*/
|
|
1769
|
+
traverseOptimized(value, callback, path = [], maxDepth = 10) {
|
|
1770
|
+
if (maxDepth <= 0) {
|
|
1771
|
+
return false;
|
|
1772
|
+
}
|
|
1773
|
+
// Call callback - if it returns true, stop traversal
|
|
1774
|
+
const shouldStop = callback(value, path);
|
|
1775
|
+
if (shouldStop === true) {
|
|
1776
|
+
return true;
|
|
1777
|
+
}
|
|
1778
|
+
// Fast path for primitives
|
|
1779
|
+
if (this._isPrimitive(value)) {
|
|
1780
|
+
return false;
|
|
1781
|
+
}
|
|
1782
|
+
// Handle arrays with early termination
|
|
1783
|
+
if (Array.isArray(value)) {
|
|
1784
|
+
for (let i = 0; i < value.length; i++) {
|
|
1785
|
+
const stopped = this.traverseOptimized(value[i], callback, [...path, i.toString()], maxDepth - 1);
|
|
1786
|
+
if (stopped) {
|
|
1787
|
+
return true;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
return false;
|
|
1791
|
+
}
|
|
1792
|
+
// Handle objects with early termination
|
|
1793
|
+
if (value && typeof value === 'object') {
|
|
1794
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1795
|
+
const stopped = this.traverseOptimized(val, callback, [...path, key], maxDepth - 1);
|
|
1796
|
+
if (stopped) {
|
|
1797
|
+
return true;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return false;
|
|
1801
|
+
}
|
|
1802
|
+
return false;
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Get cache statistics
|
|
1806
|
+
*/
|
|
1807
|
+
getCacheStats() {
|
|
1808
|
+
const total = this._cacheHits + this._cacheMisses;
|
|
1809
|
+
return {
|
|
1810
|
+
hits: this._cacheHits,
|
|
1811
|
+
misses: this._cacheMisses,
|
|
1812
|
+
hitRatio: total > 0 ? this._cacheHits / total : 0,
|
|
1813
|
+
size: this._cache.size + this._refCache.size
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Clear caches
|
|
1818
|
+
*/
|
|
1819
|
+
clearCache() {
|
|
1820
|
+
this._cache.clear();
|
|
1821
|
+
this._refCache.clear();
|
|
1822
|
+
this._cacheHits = 0;
|
|
1823
|
+
this._cacheMisses = 0;
|
|
1824
|
+
}
|
|
1825
|
+
/**
|
|
1826
|
+
* Set maximum cache size
|
|
1827
|
+
*/
|
|
1828
|
+
setMaxCacheSize(size) {
|
|
1829
|
+
this._maxCacheSize = size;
|
|
1830
|
+
// Trim caches if they're too large
|
|
1831
|
+
if (this._cache.size > size) {
|
|
1832
|
+
const entries = Array.from(this._cache.entries());
|
|
1833
|
+
this._cache.clear();
|
|
1834
|
+
// Keep the most recent entries
|
|
1835
|
+
for (const [key, value] of entries.slice(-size)) {
|
|
1836
|
+
this._cache.set(key, value);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
if (this._refCache.size > size) {
|
|
1840
|
+
const entries = Array.from(this._refCache.entries());
|
|
1841
|
+
this._refCache.clear();
|
|
1842
|
+
// Keep the most recent entries
|
|
1843
|
+
for (const [key, value] of entries.slice(-size)) {
|
|
1844
|
+
this._refCache.set(key, value);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Uncached KubernetesRef detection with optimizations
|
|
1850
|
+
*/
|
|
1851
|
+
_containsKubernetesRefsUncached(value) {
|
|
1852
|
+
// Fast path for primitives
|
|
1853
|
+
if (this._isPrimitive(value)) {
|
|
1854
|
+
return false;
|
|
1855
|
+
}
|
|
1856
|
+
// Check if this is a KubernetesRef
|
|
1857
|
+
if (this.isKubernetesRef(value)) {
|
|
1858
|
+
return true;
|
|
1859
|
+
}
|
|
1860
|
+
// Use optimized traversal with early termination
|
|
1861
|
+
return this.traverseOptimized(value, (val) => {
|
|
1862
|
+
if (this.isKubernetesRef(val)) {
|
|
1863
|
+
return true; // Stop traversal, we found one
|
|
1864
|
+
}
|
|
1865
|
+
return false; // Continue traversal
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Uncached KubernetesRef extraction with optimizations
|
|
1870
|
+
*/
|
|
1871
|
+
_extractKubernetesRefsUncached(value) {
|
|
1872
|
+
const refs = [];
|
|
1873
|
+
this.traverseOptimized(value, (val) => {
|
|
1874
|
+
if (this.isKubernetesRef(val)) {
|
|
1875
|
+
refs.push(val);
|
|
1876
|
+
}
|
|
1877
|
+
return false; // Continue traversal to find all refs
|
|
1878
|
+
});
|
|
1879
|
+
return refs;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Generate cache key for a value
|
|
1883
|
+
*/
|
|
1884
|
+
_generateCacheKey(value) {
|
|
1885
|
+
if (typeof value === 'string') {
|
|
1886
|
+
return `str:${value.length}:${value.slice(0, 50)}`;
|
|
1887
|
+
}
|
|
1888
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
1889
|
+
return `prim:${String(value)}`;
|
|
1890
|
+
}
|
|
1891
|
+
if (value === null || value === undefined) {
|
|
1892
|
+
return `null:${String(value)}`;
|
|
1893
|
+
}
|
|
1894
|
+
if (typeof value === 'function') {
|
|
1895
|
+
return `func:${value.name || 'anonymous'}:${value.toString().slice(0, 50)}`;
|
|
1896
|
+
}
|
|
1897
|
+
// For objects and arrays, use a hash of the JSON representation
|
|
1898
|
+
try {
|
|
1899
|
+
const json = JSON.stringify(value);
|
|
1900
|
+
return `obj:${json.length}:${this._simpleHash(json)}`;
|
|
1901
|
+
}
|
|
1902
|
+
catch {
|
|
1903
|
+
return `obj:unstringifiable:${Date.now()}`;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Simple hash function for cache keys
|
|
1908
|
+
*/
|
|
1909
|
+
_simpleHash(str) {
|
|
1910
|
+
let hash = 0;
|
|
1911
|
+
for (let i = 0; i < str.length; i++) {
|
|
1912
|
+
const char = str.charCodeAt(i);
|
|
1913
|
+
hash = ((hash << 5) - hash) + char;
|
|
1914
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
1915
|
+
}
|
|
1916
|
+
return Math.abs(hash).toString(36);
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Fast primitive check
|
|
1920
|
+
*/
|
|
1921
|
+
_isPrimitive(value) {
|
|
1922
|
+
return value === null ||
|
|
1923
|
+
value === undefined ||
|
|
1924
|
+
typeof value === 'string' ||
|
|
1925
|
+
typeof value === 'number' ||
|
|
1926
|
+
typeof value === 'boolean' ||
|
|
1927
|
+
typeof value === 'function';
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Fast KubernetesRef check
|
|
1931
|
+
*/
|
|
1932
|
+
isKubernetesRef(value) {
|
|
1933
|
+
return ((typeof value === 'object' || typeof value === 'function') &&
|
|
1934
|
+
value !== null &&
|
|
1935
|
+
KUBERNETES_REF_BRAND in value);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Optimized expression traverser for complex nested structures
|
|
1940
|
+
*/
|
|
1941
|
+
export class OptimizedExpressionTraverser {
|
|
1942
|
+
_detector;
|
|
1943
|
+
_visitedObjects = new WeakSet();
|
|
1944
|
+
constructor(detector) {
|
|
1945
|
+
this._detector = detector || new OptimizedKubernetesRefDetector();
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Traverse expression tree with cycle detection and optimization
|
|
1949
|
+
*/
|
|
1950
|
+
traverse(expression, visitor, options = {}) {
|
|
1951
|
+
const { maxDepth = 20, detectCycles = true, earlyTermination = true } = options;
|
|
1952
|
+
const result = {
|
|
1953
|
+
visited: 0,
|
|
1954
|
+
skipped: 0,
|
|
1955
|
+
kubernetesRefs: [],
|
|
1956
|
+
maxDepthReached: false,
|
|
1957
|
+
cyclesDetected: 0,
|
|
1958
|
+
duplicatesSkipped: 0
|
|
1959
|
+
};
|
|
1960
|
+
// WeakSet doesn't have a clear method, create a new instance
|
|
1961
|
+
this._visitedObjects = new WeakSet();
|
|
1962
|
+
const traverse = (value, path, depth) => {
|
|
1963
|
+
// Check depth limit
|
|
1964
|
+
if (depth > maxDepth) {
|
|
1965
|
+
result.maxDepthReached = true;
|
|
1966
|
+
return false;
|
|
1967
|
+
}
|
|
1968
|
+
// Cycle detection
|
|
1969
|
+
if (detectCycles && value && typeof value === 'object') {
|
|
1970
|
+
if (this._visitedObjects.has(value)) {
|
|
1971
|
+
result.cyclesDetected++;
|
|
1972
|
+
result.skipped++;
|
|
1973
|
+
return false;
|
|
1974
|
+
}
|
|
1975
|
+
this._visitedObjects.add(value);
|
|
1976
|
+
}
|
|
1977
|
+
result.visited++;
|
|
1978
|
+
// Check if this is a KubernetesRef
|
|
1979
|
+
if (this._detector.isKubernetesRef(value)) {
|
|
1980
|
+
result.kubernetesRefs.push(value);
|
|
1981
|
+
}
|
|
1982
|
+
// Call visitor
|
|
1983
|
+
const context = {
|
|
1984
|
+
depth,
|
|
1985
|
+
isKubernetesRef: this._detector.isKubernetesRef(value),
|
|
1986
|
+
hasKubernetesRefs: this._detector.containsKubernetesRefs(value, false),
|
|
1987
|
+
path: [...path]
|
|
1988
|
+
};
|
|
1989
|
+
const action = visitor(value, path, context);
|
|
1990
|
+
// Handle visitor actions
|
|
1991
|
+
switch (action) {
|
|
1992
|
+
case TraversalAction.STOP:
|
|
1993
|
+
return true; // Stop entire traversal
|
|
1994
|
+
case TraversalAction.SKIP:
|
|
1995
|
+
result.skipped++;
|
|
1996
|
+
return false; // Skip this subtree
|
|
1997
|
+
default:
|
|
1998
|
+
break; // Continue normal traversal
|
|
1999
|
+
}
|
|
2000
|
+
// Traverse children
|
|
2001
|
+
if (Array.isArray(value)) {
|
|
2002
|
+
for (let i = 0; i < value.length; i++) {
|
|
2003
|
+
const stopped = traverse(value[i], [...path, i.toString()], depth + 1);
|
|
2004
|
+
if (stopped && earlyTermination) {
|
|
2005
|
+
return true;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
else if (value && typeof value === 'object') {
|
|
2010
|
+
for (const [key, val] of Object.entries(value)) {
|
|
2011
|
+
const stopped = traverse(val, [...path, key], depth + 1);
|
|
2012
|
+
if (stopped && earlyTermination) {
|
|
2013
|
+
return true;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
return false;
|
|
2018
|
+
};
|
|
2019
|
+
traverse(expression, [], 0);
|
|
2020
|
+
return result;
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Find all KubernetesRef objects efficiently
|
|
2024
|
+
*/
|
|
2025
|
+
findAllKubernetesRefs(expression, maxDepth = 20) {
|
|
2026
|
+
const refs = [];
|
|
2027
|
+
this.traverse(expression, (value, _path, context) => {
|
|
2028
|
+
if (context.isKubernetesRef) {
|
|
2029
|
+
refs.push(value);
|
|
2030
|
+
}
|
|
2031
|
+
return TraversalAction.CONTINUE;
|
|
2032
|
+
}, { maxDepth, detectCycles: true, earlyTermination: false });
|
|
2033
|
+
return refs;
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Check if expression contains KubernetesRefs efficiently
|
|
2037
|
+
*/
|
|
2038
|
+
hasKubernetesRefs(expression, maxDepth = 20) {
|
|
2039
|
+
let found = false;
|
|
2040
|
+
this.traverse(expression, (_value, _path, context) => {
|
|
2041
|
+
if (context.isKubernetesRef) {
|
|
2042
|
+
found = true;
|
|
2043
|
+
return TraversalAction.STOP; // Early termination
|
|
2044
|
+
}
|
|
2045
|
+
return TraversalAction.CONTINUE;
|
|
2046
|
+
}, { maxDepth, detectCycles: true, earlyTermination: true });
|
|
2047
|
+
return found;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Traversal action returned by visitor functions
|
|
2052
|
+
*/
|
|
2053
|
+
export var TraversalAction;
|
|
2054
|
+
(function (TraversalAction) {
|
|
2055
|
+
/** Continue normal traversal */
|
|
2056
|
+
TraversalAction["CONTINUE"] = "continue";
|
|
2057
|
+
/** Skip this subtree */
|
|
2058
|
+
TraversalAction["SKIP"] = "skip";
|
|
2059
|
+
/** Stop entire traversal */
|
|
2060
|
+
TraversalAction["STOP"] = "stop";
|
|
2061
|
+
})(TraversalAction || (TraversalAction = {}));
|
|
2062
|
+
/**
|
|
2063
|
+
* Global optimized KubernetesRef detector
|
|
2064
|
+
*/
|
|
2065
|
+
export const globalOptimizedDetector = new OptimizedKubernetesRefDetector();
|
|
2066
|
+
/**
|
|
2067
|
+
* Expression complexity analyzer with warnings for magic proxy usage
|
|
2068
|
+
*/
|
|
2069
|
+
export class ExpressionComplexityAnalyzer {
|
|
2070
|
+
_detector;
|
|
2071
|
+
_traverser;
|
|
2072
|
+
_thresholds;
|
|
2073
|
+
constructor(detector, traverser, thresholds) {
|
|
2074
|
+
this._detector = detector || new OptimizedKubernetesRefDetector();
|
|
2075
|
+
this._traverser = traverser || new OptimizedExpressionTraverser();
|
|
2076
|
+
this._thresholds = {
|
|
2077
|
+
low: 5,
|
|
2078
|
+
medium: 15,
|
|
2079
|
+
high: 30,
|
|
2080
|
+
extreme: 50,
|
|
2081
|
+
...thresholds
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
/**
|
|
2085
|
+
* Analyze expression complexity and generate warnings
|
|
2086
|
+
*/
|
|
2087
|
+
analyzeComplexity(expression) {
|
|
2088
|
+
const startTime = performance.now();
|
|
2089
|
+
// Calculate various complexity metrics
|
|
2090
|
+
const syntacticComplexity = this._calculateSyntacticComplexity(expression);
|
|
2091
|
+
const structuralComplexity = this._calculateStructuralComplexity(expression);
|
|
2092
|
+
const magicProxyComplexity = this._calculateMagicProxyComplexity(expression);
|
|
2093
|
+
const cyclomaticComplexity = this._calculateCyclomaticComplexity(expression);
|
|
2094
|
+
// Overall complexity score
|
|
2095
|
+
const overallComplexity = Math.max(syntacticComplexity, structuralComplexity, magicProxyComplexity, cyclomaticComplexity);
|
|
2096
|
+
// Determine complexity level
|
|
2097
|
+
const level = this._determineComplexityLevel(overallComplexity);
|
|
2098
|
+
// Generate warnings
|
|
2099
|
+
const warnings = this._generateWarnings(expression, {
|
|
2100
|
+
syntacticComplexity,
|
|
2101
|
+
structuralComplexity,
|
|
2102
|
+
magicProxyComplexity,
|
|
2103
|
+
cyclomaticComplexity,
|
|
2104
|
+
overallComplexity,
|
|
2105
|
+
level
|
|
2106
|
+
});
|
|
2107
|
+
// Generate recommendations
|
|
2108
|
+
const recommendations = this._generateRecommendations(expression, {
|
|
2109
|
+
syntacticComplexity,
|
|
2110
|
+
structuralComplexity,
|
|
2111
|
+
magicProxyComplexity,
|
|
2112
|
+
cyclomaticComplexity,
|
|
2113
|
+
overallComplexity,
|
|
2114
|
+
level
|
|
2115
|
+
});
|
|
2116
|
+
const endTime = performance.now();
|
|
2117
|
+
return {
|
|
2118
|
+
expression: String(expression),
|
|
2119
|
+
syntacticComplexity,
|
|
2120
|
+
structuralComplexity,
|
|
2121
|
+
magicProxyComplexity,
|
|
2122
|
+
cyclomaticComplexity,
|
|
2123
|
+
overallComplexity,
|
|
2124
|
+
level,
|
|
2125
|
+
warnings,
|
|
2126
|
+
recommendations,
|
|
2127
|
+
analysisTime: endTime - startTime,
|
|
2128
|
+
kubernetesRefCount: this._detector.extractKubernetesRefs(expression).length,
|
|
2129
|
+
estimatedConversionTime: this._estimateConversionTime(overallComplexity),
|
|
2130
|
+
memoryImpact: this._estimateMemoryImpact(expression)
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* Batch analyze multiple expressions
|
|
2135
|
+
*/
|
|
2136
|
+
batchAnalyzeComplexity(expressions) {
|
|
2137
|
+
const results = new Map();
|
|
2138
|
+
for (const { key, expression } of expressions) {
|
|
2139
|
+
results.set(key, this.analyzeComplexity(expression));
|
|
2140
|
+
}
|
|
2141
|
+
return results;
|
|
2142
|
+
}
|
|
2143
|
+
/**
|
|
2144
|
+
* Get complexity statistics for a set of expressions
|
|
2145
|
+
*/
|
|
2146
|
+
getComplexityStats(expressions) {
|
|
2147
|
+
const results = this.batchAnalyzeComplexity(expressions);
|
|
2148
|
+
const analyses = Array.from(results.values());
|
|
2149
|
+
if (analyses.length === 0) {
|
|
2150
|
+
return {
|
|
2151
|
+
totalExpressions: 0,
|
|
2152
|
+
averageComplexity: 0,
|
|
2153
|
+
maxComplexity: 0,
|
|
2154
|
+
minComplexity: 0,
|
|
2155
|
+
complexityDistribution: { low: 0, medium: 0, high: 0, extreme: 0 },
|
|
2156
|
+
totalWarnings: 0,
|
|
2157
|
+
averageWarnings: 0,
|
|
2158
|
+
mostComplexExpression: null,
|
|
2159
|
+
totalKubernetesRefs: 0,
|
|
2160
|
+
averageKubernetesRefs: 0
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
const complexities = analyses.map(a => a.overallComplexity);
|
|
2164
|
+
const totalComplexity = complexities.reduce((sum, c) => sum + c, 0);
|
|
2165
|
+
const totalWarnings = analyses.reduce((sum, a) => sum + a.warnings.length, 0);
|
|
2166
|
+
const totalKubernetesRefs = analyses.reduce((sum, a) => sum + a.kubernetesRefCount, 0);
|
|
2167
|
+
const distribution = { low: 0, medium: 0, high: 0, extreme: 0 };
|
|
2168
|
+
for (const analysis of analyses) {
|
|
2169
|
+
distribution[analysis.level]++;
|
|
2170
|
+
}
|
|
2171
|
+
const mostComplex = analyses.reduce((max, current) => current.overallComplexity > max.overallComplexity ? current : max);
|
|
2172
|
+
return {
|
|
2173
|
+
totalExpressions: analyses.length,
|
|
2174
|
+
averageComplexity: totalComplexity / analyses.length,
|
|
2175
|
+
maxComplexity: Math.max(...complexities),
|
|
2176
|
+
minComplexity: Math.min(...complexities),
|
|
2177
|
+
complexityDistribution: distribution,
|
|
2178
|
+
totalWarnings,
|
|
2179
|
+
averageWarnings: totalWarnings / analyses.length,
|
|
2180
|
+
mostComplexExpression: mostComplex,
|
|
2181
|
+
totalKubernetesRefs,
|
|
2182
|
+
averageKubernetesRefs: totalKubernetesRefs / analyses.length
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Calculate syntactic complexity (based on syntax patterns)
|
|
2187
|
+
*/
|
|
2188
|
+
_calculateSyntacticComplexity(expression) {
|
|
2189
|
+
if (typeof expression !== 'string') {
|
|
2190
|
+
return 1;
|
|
2191
|
+
}
|
|
2192
|
+
let complexity = Math.min(expression.length / 50, 10); // Base complexity from length
|
|
2193
|
+
// Add complexity for various syntax patterns
|
|
2194
|
+
const patterns = [
|
|
2195
|
+
{ pattern: /\?\./g, weight: 2, name: 'optional chaining' },
|
|
2196
|
+
{ pattern: /\?\?/g, weight: 2, name: 'nullish coalescing' },
|
|
2197
|
+
{ pattern: /\$\{[^}]+\}/g, weight: 3, name: 'template literals' },
|
|
2198
|
+
{ pattern: /\w+\.\w+/g, weight: 1, name: 'property access' },
|
|
2199
|
+
{ pattern: /\[[^\]]+\]/g, weight: 1.5, name: 'array access' },
|
|
2200
|
+
{ pattern: /\?\s*:/g, weight: 2, name: 'ternary operator' },
|
|
2201
|
+
{ pattern: /&&|\|\|/g, weight: 1.5, name: 'logical operators' },
|
|
2202
|
+
{ pattern: /===|!==|==|!=/g, weight: 1, name: 'comparison operators' },
|
|
2203
|
+
{ pattern: /\w+\([^)]*\)/g, weight: 2.5, name: 'function calls' },
|
|
2204
|
+
{ pattern: /\bfind\b|\bfilter\b|\bmap\b|\breduce\b/g, weight: 3, name: 'array methods' }
|
|
2205
|
+
];
|
|
2206
|
+
for (const { pattern, weight } of patterns) {
|
|
2207
|
+
const matches = expression.match(pattern);
|
|
2208
|
+
if (matches) {
|
|
2209
|
+
complexity += matches.length * weight;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
return Math.min(complexity, 50); // Cap at 50
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Calculate structural complexity (based on nesting and object structure)
|
|
2216
|
+
*/
|
|
2217
|
+
_calculateStructuralComplexity(expression) {
|
|
2218
|
+
let complexity = 1;
|
|
2219
|
+
let maxDepth = 0;
|
|
2220
|
+
let nodeCount = 0;
|
|
2221
|
+
const traversalResult = this._traverser.traverse(expression, (value, _path, context) => {
|
|
2222
|
+
nodeCount++;
|
|
2223
|
+
maxDepth = Math.max(maxDepth, context.depth);
|
|
2224
|
+
// Add complexity for different node types
|
|
2225
|
+
if (Array.isArray(value)) {
|
|
2226
|
+
complexity += value.length * 0.5;
|
|
2227
|
+
}
|
|
2228
|
+
else if (value && typeof value === 'object') {
|
|
2229
|
+
complexity += Object.keys(value).length * 0.3;
|
|
2230
|
+
}
|
|
2231
|
+
return TraversalAction.CONTINUE;
|
|
2232
|
+
}, { maxDepth: 20 });
|
|
2233
|
+
// Factor in depth and node count
|
|
2234
|
+
complexity += maxDepth * 2;
|
|
2235
|
+
complexity += nodeCount * 0.1;
|
|
2236
|
+
// Penalize cycles
|
|
2237
|
+
complexity += traversalResult.cyclesDetected * 5;
|
|
2238
|
+
return Math.min(complexity, 50);
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Calculate magic proxy complexity (based on KubernetesRef usage)
|
|
2242
|
+
*/
|
|
2243
|
+
_calculateMagicProxyComplexity(expression) {
|
|
2244
|
+
const refs = this._detector.extractKubernetesRefs(expression);
|
|
2245
|
+
let complexity = refs.length * 2; // Base complexity per ref
|
|
2246
|
+
// Analyze ref patterns
|
|
2247
|
+
const resourceIds = new Set(refs.map(ref => ref.resourceId));
|
|
2248
|
+
const fieldPaths = refs.map(ref => ref.fieldPath);
|
|
2249
|
+
// Add complexity for multiple resources
|
|
2250
|
+
complexity += resourceIds.size * 1.5;
|
|
2251
|
+
// Add complexity for deep field paths
|
|
2252
|
+
for (const fieldPath of fieldPaths) {
|
|
2253
|
+
const depth = fieldPath.split('.').length;
|
|
2254
|
+
complexity += Math.max(0, depth - 2) * 0.5; // Penalize deep paths
|
|
2255
|
+
}
|
|
2256
|
+
// Add complexity for optional chaining in field paths
|
|
2257
|
+
const optionalPaths = fieldPaths.filter(path => path.includes('?'));
|
|
2258
|
+
complexity += optionalPaths.length * 1.5;
|
|
2259
|
+
return Math.min(complexity, 50);
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Calculate cyclomatic complexity (based on control flow)
|
|
2263
|
+
*/
|
|
2264
|
+
_calculateCyclomaticComplexity(expression) {
|
|
2265
|
+
if (typeof expression !== 'string') {
|
|
2266
|
+
return 1;
|
|
2267
|
+
}
|
|
2268
|
+
let complexity = 1; // Base complexity
|
|
2269
|
+
// Count decision points
|
|
2270
|
+
const decisionPatterns = [
|
|
2271
|
+
/\?\s*:/g, // Ternary operators
|
|
2272
|
+
/&&/g, // Logical AND
|
|
2273
|
+
/\|\|/g, // Logical OR
|
|
2274
|
+
/\bif\b/g, // If statements (in case of function expressions)
|
|
2275
|
+
/\belse\b/g, // Else statements
|
|
2276
|
+
/\bswitch\b/g, // Switch statements
|
|
2277
|
+
/\bcase\b/g, // Case statements
|
|
2278
|
+
/\bwhile\b/g, // While loops
|
|
2279
|
+
/\bfor\b/g, // For loops
|
|
2280
|
+
/\btry\b/g, // Try blocks
|
|
2281
|
+
/\bcatch\b/g // Catch blocks
|
|
2282
|
+
];
|
|
2283
|
+
for (const pattern of decisionPatterns) {
|
|
2284
|
+
const matches = expression.match(pattern);
|
|
2285
|
+
if (matches) {
|
|
2286
|
+
complexity += matches.length;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
return Math.min(complexity, 20);
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Determine complexity level
|
|
2293
|
+
*/
|
|
2294
|
+
_determineComplexityLevel(complexity) {
|
|
2295
|
+
if (complexity <= this._thresholds.low)
|
|
2296
|
+
return 'low';
|
|
2297
|
+
if (complexity <= this._thresholds.medium)
|
|
2298
|
+
return 'medium';
|
|
2299
|
+
if (complexity <= this._thresholds.high)
|
|
2300
|
+
return 'high';
|
|
2301
|
+
return 'extreme';
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Generate warnings based on complexity analysis
|
|
2305
|
+
*/
|
|
2306
|
+
_generateWarnings(_expression, metrics) {
|
|
2307
|
+
const warnings = [];
|
|
2308
|
+
// Overall complexity warnings
|
|
2309
|
+
if (metrics.overallComplexity > this._thresholds.high) {
|
|
2310
|
+
warnings.push({
|
|
2311
|
+
type: 'high-complexity',
|
|
2312
|
+
severity: metrics.overallComplexity > this._thresholds.extreme ? 'error' : 'warning',
|
|
2313
|
+
message: `Expression has very high complexity (${metrics.overallComplexity.toFixed(1)}). Consider breaking it down into smaller parts.`,
|
|
2314
|
+
metric: 'overallComplexity',
|
|
2315
|
+
value: metrics.overallComplexity,
|
|
2316
|
+
threshold: this._thresholds.high
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
// Magic proxy specific warnings
|
|
2320
|
+
if (metrics.magicProxyComplexity > 10) {
|
|
2321
|
+
warnings.push({
|
|
2322
|
+
type: 'magic-proxy-complexity',
|
|
2323
|
+
severity: 'warning',
|
|
2324
|
+
message: `High magic proxy usage complexity (${metrics.magicProxyComplexity.toFixed(1)}). Consider reducing KubernetesRef dependencies.`,
|
|
2325
|
+
metric: 'magicProxyComplexity',
|
|
2326
|
+
value: metrics.magicProxyComplexity,
|
|
2327
|
+
threshold: 10
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
// Syntactic complexity warnings
|
|
2331
|
+
if (metrics.syntacticComplexity > 20) {
|
|
2332
|
+
warnings.push({
|
|
2333
|
+
type: 'syntactic-complexity',
|
|
2334
|
+
severity: 'info',
|
|
2335
|
+
message: `Complex syntax patterns detected (${metrics.syntacticComplexity.toFixed(1)}). Consider simplifying the expression.`,
|
|
2336
|
+
metric: 'syntacticComplexity',
|
|
2337
|
+
value: metrics.syntacticComplexity,
|
|
2338
|
+
threshold: 20
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
// Structural complexity warnings
|
|
2342
|
+
if (metrics.structuralComplexity > 15) {
|
|
2343
|
+
warnings.push({
|
|
2344
|
+
type: 'structural-complexity',
|
|
2345
|
+
severity: 'info',
|
|
2346
|
+
message: `Deep or complex object structure (${metrics.structuralComplexity.toFixed(1)}). Consider flattening the structure.`,
|
|
2347
|
+
metric: 'structuralComplexity',
|
|
2348
|
+
value: metrics.structuralComplexity,
|
|
2349
|
+
threshold: 15
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
return warnings;
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Generate recommendations for reducing complexity
|
|
2356
|
+
*/
|
|
2357
|
+
_generateRecommendations(_expression, metrics) {
|
|
2358
|
+
const recommendations = [];
|
|
2359
|
+
if (metrics.overallComplexity > this._thresholds.high) {
|
|
2360
|
+
recommendations.push('Break down the expression into smaller, more manageable parts');
|
|
2361
|
+
recommendations.push('Consider extracting complex logic into separate functions');
|
|
2362
|
+
}
|
|
2363
|
+
if (metrics.magicProxyComplexity > 10) {
|
|
2364
|
+
recommendations.push('Reduce the number of KubernetesRef dependencies');
|
|
2365
|
+
recommendations.push('Consider caching frequently accessed resource fields');
|
|
2366
|
+
recommendations.push('Use direct references instead of deep field path access where possible');
|
|
2367
|
+
}
|
|
2368
|
+
if (metrics.syntacticComplexity > 20) {
|
|
2369
|
+
recommendations.push('Simplify complex syntax patterns like nested ternary operators');
|
|
2370
|
+
recommendations.push('Replace complex template literals with string concatenation');
|
|
2371
|
+
recommendations.push('Use intermediate variables for complex property access chains');
|
|
2372
|
+
}
|
|
2373
|
+
if (metrics.cyclomaticComplexity > 10) {
|
|
2374
|
+
recommendations.push('Reduce the number of conditional branches');
|
|
2375
|
+
recommendations.push('Consider using lookup tables instead of complex conditional logic');
|
|
2376
|
+
recommendations.push('Extract decision logic into separate functions');
|
|
2377
|
+
}
|
|
2378
|
+
return recommendations;
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Estimate conversion time based on complexity
|
|
2382
|
+
*/
|
|
2383
|
+
_estimateConversionTime(complexity) {
|
|
2384
|
+
// Base time + complexity factor
|
|
2385
|
+
return 1 + (complexity * 0.5);
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Estimate memory impact
|
|
2389
|
+
*/
|
|
2390
|
+
_estimateMemoryImpact(expression) {
|
|
2391
|
+
const size = JSON.stringify(expression).length;
|
|
2392
|
+
const refs = this._detector.extractKubernetesRefs(expression);
|
|
2393
|
+
const estimatedSize = size * 2 + refs.length * 100; // Rough estimation
|
|
2394
|
+
if (estimatedSize < 1000)
|
|
2395
|
+
return 'low';
|
|
2396
|
+
if (estimatedSize < 5000)
|
|
2397
|
+
return 'medium';
|
|
2398
|
+
if (estimatedSize < 20000)
|
|
2399
|
+
return 'high';
|
|
2400
|
+
return 'extreme';
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Global expression complexity analyzer
|
|
2405
|
+
*/
|
|
2406
|
+
export const globalComplexityAnalyzer = new ExpressionComplexityAnalyzer();
|
|
2407
|
+
/**
|
|
2408
|
+
* Global optimized expression traverser
|
|
2409
|
+
*/
|
|
2410
|
+
export const globalOptimizedTraverser = new OptimizedExpressionTraverser();
|
|
2411
|
+
/**
|
|
2412
|
+
* Global expression analysis profiler
|
|
2413
|
+
*/
|
|
2414
|
+
export const globalExpressionProfiler = new ExpressionAnalysisProfiler();
|
|
2415
|
+
/**
|
|
2416
|
+
* Global parallel expression analyzer
|
|
2417
|
+
*/
|
|
2418
|
+
export const globalParallelAnalyzer = new ParallelExpressionAnalyzer();
|
|
2419
|
+
/**
|
|
2420
|
+
* Global magic proxy integration instance
|
|
2421
|
+
*/
|
|
2422
|
+
export const globalMagicProxyIntegration = new MagicProxyLazyIntegration();
|
|
2423
|
+
/**
|
|
2424
|
+
* Global expression tree analyzer instance
|
|
2425
|
+
*/
|
|
2426
|
+
export const globalTreeAnalyzer = new ExpressionTreeAnalyzer();
|
|
2427
|
+
/**
|
|
2428
|
+
* Utility to check if a value should be wrapped in a lazy expression
|
|
2429
|
+
*/
|
|
2430
|
+
export function shouldUseLazyAnalysis(expression) {
|
|
2431
|
+
// Use lazy analysis for complex expressions or those that might contain KubernetesRef objects
|
|
2432
|
+
if (typeof expression === 'function') {
|
|
2433
|
+
return true; // Functions always need analysis
|
|
2434
|
+
}
|
|
2435
|
+
if (typeof expression === 'string' && expression.length > 50) {
|
|
2436
|
+
return true; // Long strings might be complex expressions
|
|
2437
|
+
}
|
|
2438
|
+
if (Array.isArray(expression) || (expression && typeof expression === 'object')) {
|
|
2439
|
+
return true; // Complex structures might contain KubernetesRef objects
|
|
2440
|
+
}
|
|
2441
|
+
return containsKubernetesRefs(expression);
|
|
2442
|
+
}
|
|
2443
|
+
//# sourceMappingURL=lazy-analysis.js.map
|