@@ -7,7 +7,6 @@ Now we have solved this problem for you. We have designed a simple and easy-to-u
77## Features
88
99- 🔒 ** Secure by default** - No access to global objects or prototype chain, does not use ` eval ` or ` new Function `
10- - ⚡ ** Async support** - All evaluation operations return Promises, supporting asynchronous computation and timeout protection
1110- 🚀 ** High performance** - Supports pre-compilation of expressions for improved performance with repeated evaluations
1211- 🛠️ ** Extensible** - Register custom functions to easily extend functionality
1312- 🪩 ** Lightweight** - Zero dependencies, small footprint
@@ -24,36 +23,54 @@ pnpm add @antv/expr
2423
2524## Basic Usage
2625
27- ### Asynchronous Expression Evaluation
26+ ### Synchronous Expression Evaluation
2827
2928``` typescript
3029import { evaluate } from ' @antv/expr' ;
3130
3231// Basic evaluation
33- const result = await evaluate (' x + y' , { x: 10 , y: 20 }); // returns 30
32+ const result = evaluate (' x + y' , { x: 10 , y: 20 }); // returns 30
3433
3534// Using dot notation and array access
3635const data = {
3736 values: [1 , 2 , 3 ],
3837 status: ' active'
3938};
4039
41- const result = await evaluate (' data.values[0] + data.values[1]' , { data }); // returns 3
40+ const result = evaluate (' data.values[0] + data.values[1]' , { data }); // returns 3
4241```
4342
4443### Pre-compiling Expressions
4544
4645``` typescript
47- import { compileSync , compile } from ' @antv/expr' ;
46+ import { compile } from ' @antv/expr' ;
4847
49- // Synchronous compilation (returns async execution function)
50- const evaluator = compileSync (' price * quantity' );
51- const result1 = await evaluator ({ price: 10 , quantity: 5 }); // returns 50
52- const result2 = await evaluator ({ price: 20 , quantity: 3 }); // returns 60
48+ // Compile an expression
49+ const evaluator = compile (' price * quantity' );
50+ const result1 = evaluator ({ price: 10 , quantity: 5 }); // returns 50
51+ const result2 = evaluator ({ price: 20 , quantity: 3 }); // returns 60
52+ ```
53+
54+ ### Using Asynchronous Patterns (Optional)
55+
56+ While the library is synchronous, you can still use asynchronous patterns if needed:
57+
58+ ``` typescript
59+ import { evaluate } from ' @antv/expr' ;
5360
54- // Asynchronous compilation
55- const asyncEvaluator = await compile (' price * quantity' );
56- const result = await asyncEvaluator ({ price: 15 , quantity: 2 }); // returns 30
61+ // Wrap evaluation in an async function
62+ async function asyncEvaluate(expr , context ) {
63+ return new Promise ((resolve , reject ) => {
64+ try {
65+ resolve (evaluate (expr , context ));
66+ } catch (error ) {
67+ reject (error );
68+ }
69+ });
70+ }
71+
72+ // Use with async/await
73+ const result = await asyncEvaluate (' x + y' , { x: 10 , y: 20 }); // returns 30
5774```
5875
5976### Registering Custom Functions
@@ -66,8 +83,8 @@ register('sum', (...args) => args.reduce((a, b) => a + b, 0));
6683register (' average' , (array ) => array .reduce ((a , b ) => a + b , 0 ) / array .length );
6784
6885// Use custom functions in expressions
69- const result = await evaluate (' @sum(1, 2, 3, 4)' ); // returns 10
70- const avg = await evaluate (' @average(data.values)' , {
86+ const result = evaluate (' @sum(1, 2, 3, 4)' ); // returns 10
87+ const avg = evaluate (' @average(data.values)' , {
7188 data: { values: [10 , 20 , 30 , 40 ] }
7289}); // returns 25
7390```
@@ -78,18 +95,18 @@ const avg = await evaluate('@average(data.values)', {
7895
7996``` typescript
8097// Simple variable reference
81- const result = await evaluate (' x' , { x: 42 }); // returns 42
98+ const result = evaluate (' x' , { x: 42 }); // returns 42
8299
83100// Nested property access with dot notation
84- const result = await evaluate (' user.profile.name' , {
101+ const result = evaluate (' user.profile.name' , {
85102 user: { profile: { name: ' John' } }
86103}); // returns 'John'
87104
88105// Array access with bracket notation
89- const result = await evaluate (' items[0]' , { items: [10 , 20 , 30 ] }); // returns 10
106+ const result = evaluate (' items[0]' , { items: [10 , 20 , 30 ] }); // returns 10
90107
91108// Mixed dot and bracket notation
92- const result = await evaluate (' data.items[0].value' , {
109+ const result = evaluate (' data.items[0].value' , {
93110 data: { items: [{ value: 42 }] }
94111}); // returns 42
95112```
@@ -98,28 +115,28 @@ const result = await evaluate('data.items[0].value', {
98115
99116``` typescript
100117// Basic arithmetic
101- const result = await evaluate (' a + b * c' , { a: 5 , b: 3 , c: 2 }); // returns 11
118+ const result = evaluate (' a + b * c' , { a: 5 , b: 3 , c: 2 }); // returns 11
102119
103120// Using parentheses for grouping
104- const result = await evaluate (' (a + b) * c' , { a: 5 , b: 3 , c: 2 }); // returns 16
121+ const result = evaluate (' (a + b) * c' , { a: 5 , b: 3 , c: 2 }); // returns 16
105122
106123// Modulo operation
107- const result = await evaluate (' a % b' , { a: 10 , b: 3 }); // returns 1
124+ const result = evaluate (' a % b' , { a: 10 , b: 3 }); // returns 1
108125```
109126
110127### Comparison and Logical Operations
111128
112129``` typescript
113130// Comparison operators
114- const result = await evaluate (' age >= 18' , { age: 20 }); // returns true
131+ const result = evaluate (' age >= 18' , { age: 20 }); // returns true
115132
116133// Logical AND
117- const result = await evaluate (' isActive && !isDeleted' , {
134+ const result = evaluate (' isActive && !isDeleted' , {
118135 isActive: true , isDeleted: false
119136}); // returns true
120137
121138// Logical OR
122- const result = await evaluate (' status === "active" || status === "pending"' , {
139+ const result = evaluate (' status === "active" || status === "pending"' , {
123140 status: ' pending'
124141}); // returns true
125142```
@@ -128,12 +145,12 @@ const result = await evaluate('status === "active" || status === "pending"', {
128145
129146``` typescript
130147// Simple ternary expression
131- const result = await evaluate (' age >= 18 ? "adult" : "minor"' , {
148+ const result = evaluate (' age >= 18 ? "adult" : "minor"' , {
132149 age: 20
133150}); // returns 'adult'
134151
135152// Nested ternary expressions
136- const result = await evaluate (' score >= 90 ? "A" : score >= 80 ? "B" : "C"' , {
153+ const result = evaluate (' score >= 90 ? "A" : score >= 80 ? "B" : "C"' , {
137154 score: 85
138155}); // returns 'B'
139156```
@@ -148,10 +165,10 @@ register('max', Math.max);
148165register (' formatCurrency' , (amount ) => ` $${amount .toFixed (2 )} ` );
149166
150167// Function call with arguments
151- const result = await evaluate (' @max(a, b, c)' , { a: 5 , b: 9 , c: 2 }); // returns 9
168+ const result = evaluate (' @max(a, b, c)' , { a: 5 , b: 9 , c: 2 }); // returns 9
152169
153170// Expression as function arguments
154- const result = await evaluate (' @formatCurrency(price * quantity)' , {
171+ const result = evaluate (' @formatCurrency(price * quantity)' , {
155172 price: 10.5 , quantity: 3
156173}); // returns '$31.50'
157174```
@@ -161,50 +178,32 @@ const result = await evaluate('@formatCurrency(price * quantity)', {
161178
162179### Timeout Handling
163180
164- ``` typescript
165- import { configure , evaluate } from ' @antv/expr' ;
166-
167- // Register a setTimeout function
168- register (
169- " settimeout" ,
170- (timeout : number ) => new Promise ((resolve ) => setTimeout (resolve , timeout )),
171- );
172-
173- // Set maximum execution time to 500 milliseconds
174- configure ({ maxTimeout: 500 });
175-
176- // Try to execute a potentially long-running expression
177- try {
178- await evaluate (" @settimeout(1000)" );
179- } catch (error ) {
180- console .error (" Expression error:" , error .message ); // Will catch timeout error: Expression error: Evaluation timed out after 500ms
181- }
182- ```
183-
184- ### Security Configuration
181+ You can implement timeout handling by wrapping your evaluation in a Promise.race with a timeout:
185182
186183``` typescript
187- import { configure } from ' @antv/expr' ;
188-
189- // Custom blacklisted keywords
190- configure ({
191- blackList: new Set ([
192- ' constructor' ,
193- ' __proto__' ,
194- ' prototype' ,
195- ' eval' ,
196- ' Function' ,
197- ' this' ,
198- ' window' ,
199- ' global' ,
200- // Add custom blacklisted keywords
201- ' dangerous'
202- ])
203- });
184+ import { evaluate } from " @antv/expr" ;
185+
186+ // Create a function that evaluates with a timeout
187+ function evaluateWithTimeout(expr , context , timeoutMs ) {
188+ const evaluationPromise = new Promise ((resolve , reject ) => {
189+ try {
190+ const result = evaluate (expr , context );
191+ resolve (result );
192+ } catch (error ) {
193+ reject (error );
194+ }
195+ });
196+
197+ const timeoutPromise = new Promise ((_ , reject ) => {
198+ setTimeout (
199+ () => reject (new Error (` Evaluation timed out after ${timeoutMs }ms ` )),
200+ timeoutMs ,
201+ );
202+ });
203+
204+ return Promise .race ([evaluationPromise , timeoutPromise ]);
205+ }
204206```
205- ** Default BlackList:** ` ['constructor', '__proto__', 'prototype', 'eval', 'Function', 'this', 'window', 'global'] `
206- ** Note:** You will replace the default blacklist with your custom blacklist.
207-
208207## Security Features
209208
210209This library is designed with security in mind:
@@ -219,37 +218,22 @@ This library is designed with security in mind:
219218
220219### Core Functions
221220
222- #### ` evaluate(expression: string, context?: object): Promise< any> `
221+ #### ` evaluate(expression: string, context?: object): any `
223222
224- Asynchronously evaluates an expression and returns the result.
223+ Synchronously evaluates an expression and returns the result.
225224
226225- ` expression ` : The expression string to evaluate
227226- ` context ` : An object containing variables used in the expression (optional)
228- - Returns: A Promise that resolves to the result of the expression evaluation
227+ - Returns: The result of the expression evaluation
229228
230- #### ` compileSync (expression: string): (context?: object) => Promise< any> `
229+ #### ` compile (expression: string): (context?: object) => any`
231230
232- Synchronously compiles an expression, returning an async execution function that can be used multiple times.
231+ Synchronously compiles an expression, returning a function that can be used multiple times.
233232
234233- ` expression ` : The expression string to compile
235- - Returns: A function that accepts a context object and returns a Promise resolving to the evaluation result
234+ - Returns: A function that accepts a context object and returns the evaluation result
236235
237- #### ` compile(expression: string): Promise<(context?: object) => Promise<any>> `
238-
239- Asynchronously compiles an expression, returning an async evaluation function that can be used multiple times.
240-
241- - ` expression ` : The expression string to compile
242- - Returns: A Promise that resolves to a function that accepts a context object and returns a Promise resolving to the evaluation result
243-
244- ### Configuration and Registration
245-
246- #### ` configure(config: Partial<ExpressionConfig>): void `
247-
248- Sets global configuration for expression evaluation.
249-
250- - ` config ` : Configuration options object
251- - ` maxTimeout ` : Maximum execution time in milliseconds
252- - ` blackList ` : Set of keywords not allowed in expressions
236+ ### Registration
253237
254238#### ` register(name: string, fn: Function): void `
255239
@@ -258,7 +242,6 @@ Registers a custom function that can be used in expressions.
258242- ` name ` : Function name (used with @ prefix in expressions)
259243- ` fn ` : Function implementation
260244
261-
262245### Error Handling
263246
264247All evaluation errors throw an ` ExpressionError ` type exception with detailed error information.
0 commit comments