Skip to content

Commit 61fedf9

Browse files
Copilothotlong
andcommitted
feat(core): add formula functions to expression engine (SUM, AVG, COUNT, MIN, MAX, TODAY, NOW, DATEADD, DATEDIFF, IF, AND, OR, NOT, SWITCH, CONCAT, LEFT, RIGHT, TRIM, UPPER, LOWER)
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent f1d8fe4 commit 61fedf9

4 files changed

Lines changed: 678 additions & 8 deletions

File tree

packages/core/src/evaluator/ExpressionEvaluator.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import { ExpressionContext } from './ExpressionContext.js';
2020
import { ExpressionCache } from './ExpressionCache.js';
21+
import { FormulaFunctions } from './FormulaFunctions.js';
2122

2223
/**
2324
* Options for expression evaluation
@@ -47,8 +48,13 @@ export interface EvaluationOptions {
4748
export class ExpressionEvaluator {
4849
private context: ExpressionContext;
4950
private cache: ExpressionCache;
51+
private formulas: FormulaFunctions;
5052

51-
constructor(context?: ExpressionContext | Record<string, any>, cache?: ExpressionCache) {
53+
constructor(
54+
context?: ExpressionContext | Record<string, any>,
55+
cache?: ExpressionCache,
56+
formulas?: FormulaFunctions,
57+
) {
5258
if (context instanceof ExpressionContext) {
5359
this.context = context;
5460
} else {
@@ -57,6 +63,7 @@ export class ExpressionEvaluator {
5763

5864
// Use provided cache or create a new one
5965
this.cache = cache || new ExpressionCache();
66+
this.formulas = formulas || new FormulaFunctions();
6067
}
6168

6269
/**
@@ -139,9 +146,13 @@ export class ExpressionEvaluator {
139146
// Create a safe evaluation function
140147
const contextObj = this.context.toObject();
141148

149+
// Inject formula functions into the evaluation context
150+
const formulaObj = this.formulas.toObject();
151+
const mergedContext = { ...formulaObj, ...contextObj };
152+
142153
// Build safe function with context variables
143-
const varNames = Object.keys(contextObj);
144-
const varValues = Object.values(contextObj);
154+
const varNames = Object.keys(mergedContext);
155+
const varValues = Object.values(mergedContext);
145156

146157
// Use cached compilation
147158
const compiled = this.cache.compile(expression, varNames);
@@ -219,8 +230,8 @@ export class ExpressionEvaluator {
219230
* Create a new evaluator with additional context data
220231
*/
221232
withContext(data: Record<string, any>): ExpressionEvaluator {
222-
// Share the cache with the new evaluator for maximum efficiency
223-
return new ExpressionEvaluator(this.context.createChild(data), this.cache);
233+
// Share the cache and formulas with the new evaluator for maximum efficiency
234+
return new ExpressionEvaluator(this.context.createChild(data), this.cache, this.formulas);
224235
}
225236

226237
/**
@@ -236,12 +247,27 @@ export class ExpressionEvaluator {
236247
clearCache(): void {
237248
this.cache.clear();
238249
}
250+
251+
/**
252+
* Get the formula functions registry
253+
*/
254+
getFormulas(): FormulaFunctions {
255+
return this.formulas;
256+
}
257+
258+
/**
259+
* Register a custom formula function
260+
*/
261+
registerFunction(name: string, fn: (...args: any[]) => any): void {
262+
this.formulas.register(name, fn);
263+
}
239264
}
240265

241266
/**
242-
* Shared global cache for convenience functions
267+
* Shared global cache and formulas for convenience functions
243268
*/
244269
const globalCache = new ExpressionCache();
270+
const globalFormulas = new FormulaFunctions();
245271

246272
/**
247273
* Convenience function to quickly evaluate an expression
@@ -251,7 +277,7 @@ export function evaluateExpression(
251277
context: Record<string, any> = {},
252278
options: EvaluationOptions = {}
253279
): any {
254-
const evaluator = new ExpressionEvaluator(context, globalCache);
280+
const evaluator = new ExpressionEvaluator(context, globalCache, globalFormulas);
255281
return evaluator.evaluate(expression, options);
256282
}
257283

@@ -262,6 +288,6 @@ export function evaluateCondition(
262288
condition: string | boolean | undefined,
263289
context: Record<string, any> = {}
264290
): boolean {
265-
const evaluator = new ExpressionEvaluator(context, globalCache);
291+
const evaluator = new ExpressionEvaluator(context, globalCache, globalFormulas);
266292
return evaluator.evaluateCondition(condition);
267293
}

0 commit comments

Comments
 (0)