Skip to content

Commit 3bfa7e8

Browse files
feat: Add CelProgram.evaluate static function which compiles and evaluates in one step
1 parent 404e6bd commit 3bfa7e8

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed

__tests__/cel.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,44 @@ describe("CelProgram", () => {
8282
});
8383
});
8484
});
85+
86+
describe("CelProgram.evaluate", () => {
87+
it("should evaluate a simple expression in one step", async () => {
88+
const result = await CelProgram.evaluate("size(message) > 5", {
89+
message: "Hello World",
90+
});
91+
expect(result).toBe(true);
92+
});
93+
94+
it("should handle errors gracefully", async () => {
95+
await expect(
96+
CelProgram.evaluate("invalid expression", {}),
97+
).rejects.toThrow();
98+
});
99+
100+
it("should handle complex expressions and data structures", async () => {
101+
const result = await CelProgram.evaluate(
102+
'{"name": "test", "items": [1, 2, 3].map(i, {"id": i})}',
103+
{},
104+
);
105+
expect(result).toEqual({
106+
name: "test",
107+
items: [{ id: 1 }, { id: 2 }, { id: 3 }],
108+
});
109+
});
110+
111+
it("should handle cart validation in one step", async () => {
112+
const expr =
113+
'has(cart.items) && cart.items.exists(item, item.productId == "prod_123" && item.quantity >= 1)';
114+
const result = await CelProgram.evaluate(expr, {
115+
cart: {
116+
items: [
117+
{ productId: "prod_456", quantity: 1 },
118+
{ productId: "prod_123", quantity: 1 },
119+
{ productId: "prod_789", quantity: 3 },
120+
],
121+
},
122+
});
123+
expect(result).toBe(true);
124+
});
125+
});

src/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@ export class CelProgram {
1010
this.native = native;
1111
}
1212

13+
/**
14+
* Compiles a CEL expression into a reusable program.
15+
*
16+
* Performance advantages:
17+
* - Parsing and compilation are done only once: The CEL expression is parsed into an Abstract
18+
* Syntax Tree (AST) and compiled into an optimized internal representation in Rust
19+
* - Type checking and validation happen during compilation: Any syntax errors or type
20+
* mismatches are caught early, before execution
21+
* - The compiled program is cached in memory: The native Rust program object is retained
22+
* and can be reused without re-parsing or re-validating the expression
23+
* - Execution is optimized: When executing the compiled program, it only needs to convert
24+
* the input context to CEL values and evaluate the pre-compiled expression tree
25+
*
26+
* This is particularly beneficial when you need to evaluate the same expression multiple
27+
* times with different contexts, as you avoid the overhead of parsing, validation, and
28+
* compilation for each evaluation.
29+
*
30+
* @param source The CEL expression to compile
31+
* @returns A compiled CelProgram instance ready for execution
32+
*/
1333
static async compile(source: string): Promise<CelProgram> {
1434
if (!CelProgram.nativeModule) {
1535
// Use the NAPI-RS generated loader which handles platform detection
@@ -24,6 +44,16 @@ export class CelProgram {
2444
async execute(context: CelContext): Promise<any> {
2545
return this.native.execute(context);
2646
}
47+
48+
/**
49+
* Convenience method to compile and execute a CEL expression in one step.
50+
* Note: If you plan to evaluate the same expression multiple times with different contexts,
51+
* it's more efficient to use compile() once and then call execute() multiple times.
52+
*/
53+
static async evaluate(source: string, context: CelContext): Promise<any> {
54+
const program = await CelProgram.compile(source);
55+
return program.execute(context);
56+
}
2757
}
2858

2959
export default CelProgram;

0 commit comments

Comments
 (0)