@ram_28/kf-ai-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +840 -0
- package/dist/api/client.d.ts +78 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/datetime.d.ts +21 -0
- package/dist/api/datetime.d.ts.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/metadata.d.ts +75 -0
- package/dist/api/metadata.d.ts.map +1 -0
- package/dist/components/hooks/index.d.ts +8 -0
- package/dist/components/hooks/index.d.ts.map +1 -0
- package/dist/components/hooks/useFilter/index.d.ts +5 -0
- package/dist/components/hooks/useFilter/index.d.ts.map +1 -0
- package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts +33 -0
- package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts.map +1 -0
- package/dist/components/hooks/useFilter/types.d.ts +137 -0
- package/dist/components/hooks/useFilter/types.d.ts.map +1 -0
- package/dist/components/hooks/useFilter/useFilter.d.ts +3 -0
- package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -0
- package/dist/components/hooks/useFilter/validation.utils.d.ts +38 -0
- package/dist/components/hooks/useFilter/validation.utils.d.ts.map +1 -0
- package/dist/components/hooks/useForm/apiClient.d.ts +71 -0
- package/dist/components/hooks/useForm/apiClient.d.ts.map +1 -0
- package/dist/components/hooks/useForm/expressionValidator.utils.d.ts +28 -0
- package/dist/components/hooks/useForm/expressionValidator.utils.d.ts.map +1 -0
- package/dist/components/hooks/useForm/index.d.ts +6 -0
- package/dist/components/hooks/useForm/index.d.ts.map +1 -0
- package/dist/components/hooks/useForm/optimizedExpressionValidator.utils.d.ts +88 -0
- package/dist/components/hooks/useForm/optimizedExpressionValidator.utils.d.ts.map +1 -0
- package/dist/components/hooks/useForm/ruleClassifier.utils.d.ts +28 -0
- package/dist/components/hooks/useForm/ruleClassifier.utils.d.ts.map +1 -0
- package/dist/components/hooks/useForm/schemaParser.utils.d.ts +29 -0
- package/dist/components/hooks/useForm/schemaParser.utils.d.ts.map +1 -0
- package/dist/components/hooks/useForm/types.d.ts +412 -0
- package/dist/components/hooks/useForm/types.d.ts.map +1 -0
- package/dist/components/hooks/useForm/useForm.d.ts +3 -0
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/apiClient.d.ts +99 -0
- package/dist/components/hooks/useKanban/apiClient.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/context.d.ts +4 -0
- package/dist/components/hooks/useKanban/context.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/dragDropManager.d.ts +27 -0
- package/dist/components/hooks/useKanban/dragDropManager.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/index.d.ts +6 -0
- package/dist/components/hooks/useKanban/index.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/types.d.ts +438 -0
- package/dist/components/hooks/useKanban/types.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/useKanban.d.ts +3 -0
- package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/useKanbanSimple.d.ts +62 -0
- package/dist/components/hooks/useKanban/useKanbanSimple.d.ts.map +1 -0
- package/dist/components/hooks/useTable/index.d.ts +3 -0
- package/dist/components/hooks/useTable/index.d.ts.map +1 -0
- package/dist/components/hooks/useTable/types.d.ts +107 -0
- package/dist/components/hooks/useTable/types.d.ts.map +1 -0
- package/dist/components/hooks/useTable/useTable.d.ts +8 -0
- package/dist/components/hooks/useTable/useTable.d.ts.map +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.d.ts.map +1 -0
- package/dist/components/ui/kanban/Kanban.d.ts +12 -0
- package/dist/components/ui/kanban/Kanban.d.ts.map +1 -0
- package/dist/components/ui/kanban/index.d.ts +2 -0
- package/dist/components/ui/kanban/index.d.ts.map +1 -0
- package/dist/index.cjs +45 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +6522 -0
- package/dist/types/base-fields.d.ts +182 -0
- package/dist/types/base-fields.d.ts.map +1 -0
- package/dist/types/common.d.ts +238 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/cn.d.ts +7 -0
- package/dist/utils/cn.d.ts.map +1 -0
- package/dist/utils/formatting.d.ts +52 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/package.json +98 -0
- package/sdk/api/client.ts +447 -0
- package/sdk/api/datetime.ts +33 -0
- package/sdk/api/index.ts +61 -0
- package/sdk/api/metadata.ts +148 -0
- package/sdk/components/hooks/index.ts +34 -0
- package/sdk/components/hooks/useFilter/index.ts +37 -0
- package/sdk/components/hooks/useFilter/payloadBuilder.utils.ts +298 -0
- package/sdk/components/hooks/useFilter/types.ts +158 -0
- package/sdk/components/hooks/useFilter/useFilter.llm.txt +497 -0
- package/sdk/components/hooks/useFilter/useFilter.ts +494 -0
- package/sdk/components/hooks/useFilter/validation.utils.ts +401 -0
- package/sdk/components/hooks/useForm/apiClient.ts +441 -0
- package/sdk/components/hooks/useForm/expressionValidator.utils.ts +444 -0
- package/sdk/components/hooks/useForm/index.ts +64 -0
- package/sdk/components/hooks/useForm/optimizedExpressionValidator.utils.ts +482 -0
- package/sdk/components/hooks/useForm/ruleClassifier.utils.ts +424 -0
- package/sdk/components/hooks/useForm/schemaParser.utils.ts +519 -0
- package/sdk/components/hooks/useForm/types.ts +630 -0
- package/sdk/components/hooks/useForm/useForm.llm.txt +340 -0
- package/sdk/components/hooks/useForm/useForm.ts +821 -0
- package/sdk/components/hooks/useKanban/apiClient.ts +494 -0
- package/sdk/components/hooks/useKanban/context.ts +14 -0
- package/sdk/components/hooks/useKanban/dragDropManager.ts +529 -0
- package/sdk/components/hooks/useKanban/index.ts +63 -0
- package/sdk/components/hooks/useKanban/types.ts +606 -0
- package/sdk/components/hooks/useKanban/useKanban.llm.txt +482 -0
- package/sdk/components/hooks/useKanban/useKanban.ts +725 -0
- package/sdk/components/hooks/useKanban/useKanbanSimple.ts +389 -0
- package/sdk/components/hooks/useTable/index.ts +5 -0
- package/sdk/components/hooks/useTable/types.ts +154 -0
- package/sdk/components/hooks/useTable/useTable.llm.txt +344 -0
- package/sdk/components/hooks/useTable/useTable.ts +413 -0
- package/sdk/components/index.ts +15 -0
- package/sdk/components/ui/index.ts +2 -0
- package/sdk/components/ui/kanban/Kanban.tsx +134 -0
- package/sdk/components/ui/kanban/index.ts +11 -0
- package/sdk/index.ts +13 -0
- package/sdk/types/base-fields.ts +221 -0
- package/sdk/types/common.ts +306 -0
- package/sdk/types/index.ts +5 -0
- package/sdk/utils/cn.ts +10 -0
- package/sdk/utils/formatting.ts +212 -0
- package/sdk/utils/index.ts +5 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// OPTIMIZED EXPRESSION VALIDATOR
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Enhanced expression evaluator with caching, dependency tracking, and performance optimizations
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ExpressionTree,
|
|
8
|
+
ValidationResult,
|
|
9
|
+
ValidationRule,
|
|
10
|
+
} from "./types";
|
|
11
|
+
|
|
12
|
+
// ============================================================
|
|
13
|
+
// CACHING SYSTEM
|
|
14
|
+
// ============================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* LRU Cache for expression results
|
|
18
|
+
*/
|
|
19
|
+
class LRUCache<T> {
|
|
20
|
+
private cache = new Map<string, T>();
|
|
21
|
+
private maxSize: number;
|
|
22
|
+
|
|
23
|
+
constructor(maxSize = 1000) {
|
|
24
|
+
this.maxSize = maxSize;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get(key: string): T | undefined {
|
|
28
|
+
const value = this.cache.get(key);
|
|
29
|
+
if (value !== undefined) {
|
|
30
|
+
// Move to end (most recently used)
|
|
31
|
+
this.cache.delete(key);
|
|
32
|
+
this.cache.set(key, value);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
set(key: string, value: T): void {
|
|
38
|
+
if (this.cache.has(key)) {
|
|
39
|
+
this.cache.delete(key);
|
|
40
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
41
|
+
// Remove least recently used (first entry)
|
|
42
|
+
const firstKey = this.cache.keys().next().value as string;
|
|
43
|
+
this.cache.delete(firstKey);
|
|
44
|
+
}
|
|
45
|
+
this.cache.set(key, value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
clear(): void {
|
|
49
|
+
this.cache.clear();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ============================================================
|
|
54
|
+
// EXPRESSION DEPENDENCY ANALYZER
|
|
55
|
+
// ============================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Analyze expression dependencies for optimized watching
|
|
59
|
+
*/
|
|
60
|
+
export function analyzeExpressionDependencies(
|
|
61
|
+
expression: ExpressionTree
|
|
62
|
+
): Set<string> {
|
|
63
|
+
const dependencies = new Set<string>();
|
|
64
|
+
|
|
65
|
+
function traverse(node: ExpressionTree): void {
|
|
66
|
+
switch (node.Type) {
|
|
67
|
+
case "Identifier":
|
|
68
|
+
if (node.Name && !node.Name.startsWith("$")) {
|
|
69
|
+
dependencies.add(node.Name);
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case "MemberExpression":
|
|
74
|
+
if (node.Arguments) {
|
|
75
|
+
node.Arguments.forEach(traverse);
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case "CallExpression":
|
|
80
|
+
case "BinaryExpression":
|
|
81
|
+
case "LogicalExpression":
|
|
82
|
+
if (node.Arguments) {
|
|
83
|
+
node.Arguments.forEach(traverse);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
traverse(expression);
|
|
90
|
+
return dependencies;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build dependency graph for multiple expressions
|
|
95
|
+
*/
|
|
96
|
+
export function buildDependencyGraph(
|
|
97
|
+
rules: Record<string, ValidationRule>
|
|
98
|
+
): Map<string, Set<string>> {
|
|
99
|
+
const graph = new Map<string, Set<string>>();
|
|
100
|
+
|
|
101
|
+
Object.entries(rules).forEach(([ruleId, rule]) => {
|
|
102
|
+
const dependencies = analyzeExpressionDependencies(rule.ExpressionTree);
|
|
103
|
+
graph.set(ruleId, dependencies);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return graph;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================================
|
|
110
|
+
// OPTIMIZED EXPRESSION EVALUATOR
|
|
111
|
+
// ============================================================
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Optimized expression evaluator with caching and memoization
|
|
115
|
+
*/
|
|
116
|
+
export class OptimizedExpressionEvaluator {
|
|
117
|
+
private resultCache = new LRUCache<any>(500);
|
|
118
|
+
private dependencyCache = new LRUCache<Set<string>>(200);
|
|
119
|
+
private compiledExpressions = new Map<string, Function>();
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create cache key from expression and context
|
|
123
|
+
*/
|
|
124
|
+
private createCacheKey(
|
|
125
|
+
expression: ExpressionTree,
|
|
126
|
+
context: Record<string, any>
|
|
127
|
+
): string {
|
|
128
|
+
const expressionHash = JSON.stringify(expression);
|
|
129
|
+
const contextHash = JSON.stringify(context);
|
|
130
|
+
return `${expressionHash}:${contextHash}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get expression dependencies (cached)
|
|
135
|
+
*/
|
|
136
|
+
getDependencies(expression: ExpressionTree): Set<string> {
|
|
137
|
+
const expressionKey = JSON.stringify(expression);
|
|
138
|
+
|
|
139
|
+
let dependencies = this.dependencyCache.get(expressionKey);
|
|
140
|
+
if (!dependencies) {
|
|
141
|
+
dependencies = analyzeExpressionDependencies(expression);
|
|
142
|
+
this.dependencyCache.set(expressionKey, dependencies);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return dependencies;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if expression result is cached and context hasn't changed
|
|
150
|
+
*/
|
|
151
|
+
private getCachedResult(
|
|
152
|
+
expression: ExpressionTree,
|
|
153
|
+
context: Record<string, any>,
|
|
154
|
+
lastContext?: Record<string, any>
|
|
155
|
+
): any | undefined {
|
|
156
|
+
if (!lastContext) return undefined;
|
|
157
|
+
|
|
158
|
+
const dependencies = this.getDependencies(expression);
|
|
159
|
+
|
|
160
|
+
// Check if any dependency has changed
|
|
161
|
+
for (const dep of dependencies) {
|
|
162
|
+
if (context[dep] !== lastContext[dep]) {
|
|
163
|
+
return undefined; // Dependencies changed, cache invalid
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Dependencies unchanged, try cache
|
|
168
|
+
const cacheKey = this.createCacheKey(expression, context);
|
|
169
|
+
return this.resultCache.get(cacheKey);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Evaluate expression with caching
|
|
174
|
+
*/
|
|
175
|
+
evaluate(
|
|
176
|
+
expression: ExpressionTree,
|
|
177
|
+
context: Record<string, any>,
|
|
178
|
+
lastContext?: Record<string, any>
|
|
179
|
+
): any {
|
|
180
|
+
// Try cached result
|
|
181
|
+
const cached = this.getCachedResult(expression, context, lastContext);
|
|
182
|
+
if (cached !== undefined) {
|
|
183
|
+
return cached;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Evaluate and cache result
|
|
187
|
+
const result = this.evaluateNode(expression, context);
|
|
188
|
+
const cacheKey = this.createCacheKey(expression, context);
|
|
189
|
+
this.resultCache.set(cacheKey, result);
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Core expression evaluation logic
|
|
196
|
+
*/
|
|
197
|
+
private evaluateNode(node: ExpressionTree, context: Record<string, any>): any {
|
|
198
|
+
switch (node.Type) {
|
|
199
|
+
case "Literal":
|
|
200
|
+
return node.Value;
|
|
201
|
+
|
|
202
|
+
case "Identifier":
|
|
203
|
+
return this.getIdentifierValue(node, context);
|
|
204
|
+
|
|
205
|
+
case "BinaryExpression":
|
|
206
|
+
return this.evaluateBinaryExpression(node, context);
|
|
207
|
+
|
|
208
|
+
case "LogicalExpression":
|
|
209
|
+
return this.evaluateLogicalExpression(node, context);
|
|
210
|
+
|
|
211
|
+
case "CallExpression":
|
|
212
|
+
return this.evaluateCallExpression(node, context);
|
|
213
|
+
|
|
214
|
+
case "MemberExpression":
|
|
215
|
+
return this.evaluateMemberExpression(node, context);
|
|
216
|
+
|
|
217
|
+
default:
|
|
218
|
+
throw new Error(`Unsupported expression type: ${node.Type}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get identifier value with system context support
|
|
224
|
+
*/
|
|
225
|
+
private getIdentifierValue(node: ExpressionTree, context: Record<string, any>): any {
|
|
226
|
+
if (!node.Name) return undefined;
|
|
227
|
+
|
|
228
|
+
// System identifiers
|
|
229
|
+
if (node.Name === "NOW") return new Date();
|
|
230
|
+
if (node.Name === "TODAY") {
|
|
231
|
+
const today = new Date();
|
|
232
|
+
return new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
233
|
+
}
|
|
234
|
+
if (node.Name === "CURRENT_USER_ID") return 1; // Would come from auth context
|
|
235
|
+
if (node.Name === "CURRENT_USER") {
|
|
236
|
+
return {
|
|
237
|
+
EmpId: 1,
|
|
238
|
+
Email: "user@example.com",
|
|
239
|
+
FirstName: "John",
|
|
240
|
+
LastName: "Doe",
|
|
241
|
+
Role: "User",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Form field values
|
|
246
|
+
return context[node.Name];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Evaluate binary expressions with type coercion
|
|
251
|
+
*/
|
|
252
|
+
private evaluateBinaryExpression(node: ExpressionTree, context: Record<string, any>): any {
|
|
253
|
+
if (!node.Arguments || node.Arguments.length !== 2) {
|
|
254
|
+
throw new Error("Binary expression requires exactly 2 arguments");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const left = this.evaluateNode(node.Arguments[0], context);
|
|
258
|
+
const right = this.evaluateNode(node.Arguments[1], context);
|
|
259
|
+
|
|
260
|
+
switch (node.Operator) {
|
|
261
|
+
case "==": return left == right;
|
|
262
|
+
case "!=": return left != right;
|
|
263
|
+
case "===": return left === right;
|
|
264
|
+
case "!==": return left !== right;
|
|
265
|
+
case "<": return Number(left) < Number(right);
|
|
266
|
+
case "<=": return Number(left) <= Number(right);
|
|
267
|
+
case ">": return Number(left) > Number(right);
|
|
268
|
+
case ">=": return Number(left) >= Number(right);
|
|
269
|
+
case "+": return Number(left) + Number(right);
|
|
270
|
+
case "-": return Number(left) - Number(right);
|
|
271
|
+
case "*": return Number(left) * Number(right);
|
|
272
|
+
case "/": return Number(left) / Number(right);
|
|
273
|
+
case "%": return Number(left) % Number(right);
|
|
274
|
+
default:
|
|
275
|
+
throw new Error(`Unsupported binary operator: ${node.Operator}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Evaluate logical expressions with short-circuiting
|
|
281
|
+
*/
|
|
282
|
+
private evaluateLogicalExpression(node: ExpressionTree, context: Record<string, any>): any {
|
|
283
|
+
if (!node.Arguments || node.Arguments.length < 2) {
|
|
284
|
+
throw new Error("Logical expression requires at least 2 arguments");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
switch (node.Operator) {
|
|
288
|
+
case "AND":
|
|
289
|
+
// Short-circuit: return false on first falsy value
|
|
290
|
+
for (const arg of node.Arguments) {
|
|
291
|
+
const result = this.evaluateNode(arg, context);
|
|
292
|
+
if (!result) return false;
|
|
293
|
+
}
|
|
294
|
+
return true;
|
|
295
|
+
|
|
296
|
+
case "OR":
|
|
297
|
+
// Short-circuit: return true on first truthy value
|
|
298
|
+
for (const arg of node.Arguments) {
|
|
299
|
+
const result = this.evaluateNode(arg, context);
|
|
300
|
+
if (result) return true;
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
|
|
304
|
+
default:
|
|
305
|
+
throw new Error(`Unsupported logical operator: ${node.Operator}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Evaluate function calls with built-in functions
|
|
311
|
+
*/
|
|
312
|
+
private evaluateCallExpression(node: ExpressionTree, context: Record<string, any>): any {
|
|
313
|
+
if (!node.Callee) {
|
|
314
|
+
throw new Error("Call expression requires a function name");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const args = node.Arguments?.map(arg => this.evaluateNode(arg, context)) || [];
|
|
318
|
+
|
|
319
|
+
switch (node.Callee) {
|
|
320
|
+
// String functions
|
|
321
|
+
case "CONCAT": return args.map(a => String(a || "")).join("");
|
|
322
|
+
case "TRIM": return String(args[0] || "").trim();
|
|
323
|
+
case "LENGTH": return String(args[0] || "").length;
|
|
324
|
+
case "UPPER": return String(args[0] || "").toUpperCase();
|
|
325
|
+
case "LOWER": return String(args[0] || "").toLowerCase();
|
|
326
|
+
case "CONTAINS": return String(args[0] || "").includes(String(args[1] || ""));
|
|
327
|
+
case "MATCHES": return new RegExp(String(args[1])).test(String(args[0] || ""));
|
|
328
|
+
|
|
329
|
+
// Math functions
|
|
330
|
+
case "SUM": return args.reduce((sum, val) => sum + (Number(val) || 0), 0);
|
|
331
|
+
case "AVG":
|
|
332
|
+
const nums = args.filter(val => !isNaN(Number(val)));
|
|
333
|
+
return nums.length > 0 ? nums.reduce((sum, val) => sum + Number(val), 0) / nums.length : 0;
|
|
334
|
+
case "MIN": return Math.min(...args.map(val => Number(val) || 0));
|
|
335
|
+
case "MAX": return Math.max(...args.map(val => Number(val) || 0));
|
|
336
|
+
case "ROUND": return Math.round(Number(args[0]) || 0);
|
|
337
|
+
case "FLOOR": return Math.floor(Number(args[0]) || 0);
|
|
338
|
+
case "CEIL": return Math.ceil(Number(args[0]) || 0);
|
|
339
|
+
case "ABS": return Math.abs(Number(args[0]) || 0);
|
|
340
|
+
|
|
341
|
+
// Date functions
|
|
342
|
+
case "YEAR": return new Date(args[0]).getFullYear();
|
|
343
|
+
case "MONTH": return new Date(args[0]).getMonth() + 1;
|
|
344
|
+
case "DAY": return new Date(args[0]).getDate();
|
|
345
|
+
case "DATE_DIFF":
|
|
346
|
+
const d1 = new Date(args[0]);
|
|
347
|
+
const d2 = new Date(args[1]);
|
|
348
|
+
const diffTime = Math.abs(d1.getTime() - d2.getTime());
|
|
349
|
+
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
350
|
+
|
|
351
|
+
// Conditional functions
|
|
352
|
+
case "IF": return args[0] ? args[1] : args[2];
|
|
353
|
+
case "AUTO_NUMBER": return Math.floor(Math.random() * 10000);
|
|
354
|
+
|
|
355
|
+
default:
|
|
356
|
+
throw new Error(`Unknown function: ${node.Callee}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Evaluate member expressions
|
|
362
|
+
*/
|
|
363
|
+
private evaluateMemberExpression(node: ExpressionTree, context: Record<string, any>): any {
|
|
364
|
+
if (!node.Arguments || node.Arguments.length === 0) {
|
|
365
|
+
throw new Error("Member expression requires arguments");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const object = this.evaluateNode(node.Arguments[0], context);
|
|
369
|
+
const propertyName = node.Arguments[0].Property?.Name;
|
|
370
|
+
|
|
371
|
+
if (propertyName && object && typeof object === 'object') {
|
|
372
|
+
return object[propertyName];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return object;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Clear all caches
|
|
380
|
+
*/
|
|
381
|
+
clearCache(): void {
|
|
382
|
+
this.resultCache.clear();
|
|
383
|
+
this.dependencyCache.clear();
|
|
384
|
+
this.compiledExpressions.clear();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ============================================================
|
|
389
|
+
// OPTIMIZED VALIDATION FUNCTIONS
|
|
390
|
+
// ============================================================
|
|
391
|
+
|
|
392
|
+
// Global evaluator instance
|
|
393
|
+
const globalEvaluator = new OptimizedExpressionEvaluator();
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Optimized field validation with caching
|
|
397
|
+
*/
|
|
398
|
+
export function validateFieldOptimized<T = Record<string, any>>(
|
|
399
|
+
fieldName: string,
|
|
400
|
+
fieldValue: any,
|
|
401
|
+
validationRules: ValidationRule[],
|
|
402
|
+
formValues: T,
|
|
403
|
+
lastFormValues?: T
|
|
404
|
+
): ValidationResult<T> {
|
|
405
|
+
if (!validationRules || validationRules.length === 0) {
|
|
406
|
+
return { isValid: true };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const currentFormValues = { ...formValues, [fieldName]: fieldValue };
|
|
410
|
+
|
|
411
|
+
for (const rule of validationRules) {
|
|
412
|
+
try {
|
|
413
|
+
const isValid = globalEvaluator.evaluate(
|
|
414
|
+
rule.ExpressionTree,
|
|
415
|
+
currentFormValues,
|
|
416
|
+
lastFormValues as any
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
if (!isValid) {
|
|
420
|
+
return {
|
|
421
|
+
isValid: false,
|
|
422
|
+
message: rule.Message || `Validation failed for ${rule.Name}`,
|
|
423
|
+
fieldName: fieldName as keyof T,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.warn(`Validation rule ${rule.Id} failed to evaluate:`, error);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return { isValid: true };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Optimized computed value calculation
|
|
436
|
+
*/
|
|
437
|
+
export function calculateComputedValueOptimized(
|
|
438
|
+
expression: ExpressionTree,
|
|
439
|
+
formValues: Record<string, any>,
|
|
440
|
+
lastFormValues?: Record<string, any>
|
|
441
|
+
): any {
|
|
442
|
+
try {
|
|
443
|
+
return globalEvaluator.evaluate(expression, formValues, lastFormValues);
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.warn("Failed to calculate computed value:", error);
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get field dependencies for optimized watching
|
|
452
|
+
*/
|
|
453
|
+
export function getFieldDependencies(expression: ExpressionTree): string[] {
|
|
454
|
+
return Array.from(globalEvaluator.getDependencies(expression));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Batch validate multiple fields efficiently
|
|
459
|
+
*/
|
|
460
|
+
export function batchValidateFields<T = Record<string, any>>(
|
|
461
|
+
validations: Array<{
|
|
462
|
+
fieldName: string;
|
|
463
|
+
fieldValue: any;
|
|
464
|
+
rules: ValidationRule[];
|
|
465
|
+
}>,
|
|
466
|
+
formValues: T,
|
|
467
|
+
lastFormValues?: T
|
|
468
|
+
): Array<ValidationResult<T>> {
|
|
469
|
+
return validations.map(({ fieldName, fieldValue, rules }) =>
|
|
470
|
+
validateFieldOptimized(fieldName, fieldValue, rules, formValues, lastFormValues)
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Clear global expression cache
|
|
476
|
+
*/
|
|
477
|
+
export function clearExpressionCache(): void {
|
|
478
|
+
globalEvaluator.clearCache();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Export the evaluator for advanced usage
|
|
482
|
+
export { globalEvaluator as expressionEvaluator };
|