@axi-engine/expressions 0.2.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 +179 -0
- package/dist/index.d.mts +469 -0
- package/dist/index.d.ts +469 -0
- package/dist/index.js +319 -0
- package/dist/index.mjs +281 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# @axi-engine/expressions
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@axi-engine/expressions)
|
|
4
|
+
|
|
5
|
+
A flexible, type-safe, and extensible engine for evaluating declarative logical expressions.
|
|
6
|
+
It allows you to define complex game logic (like quest conditions, dialogue triggers, or AI behavior) in data files (e.g., JSON) instead of hard-coding it.
|
|
7
|
+
|
|
8
|
+
## Key Features
|
|
9
|
+
|
|
10
|
+
- **Declarative Logic:** Define complex conditions as data, making them easy to author, modify, and store.
|
|
11
|
+
- **Type-Safe:** Built entirely with TypeScript, providing strong type checking and autocompletion.
|
|
12
|
+
- **Extensible:** Easily add your own custom expression types and logic by creating new handlers.
|
|
13
|
+
- **Asynchronous by Design:** Core evaluation is promise-based, allowing for future async operations.
|
|
14
|
+
- **Decoupled:** Depends only on @axi-engine/utils for shared types and a simple DataSource interface, making it easy to integrate with any state management system.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @axi-engine/expressions
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Core Concepts
|
|
23
|
+
|
|
24
|
+
- **`Expression`**: A plain JavaScript object that defines a logical condition (e.g., `comparison`, `and`, `or`).
|
|
25
|
+
- **`DataSource`**: A simple interface (`{ get(path), has(path) }`) that provides the data against which expressions are evaluated. This can be your game's state manager, a local scope, or any other data source.
|
|
26
|
+
- **`ExpressionEvaluator`**: The main class that takes an `Expression` and a `DataSource` and resolves them to a boolean result.
|
|
27
|
+
|
|
28
|
+
## Basic Usage
|
|
29
|
+
|
|
30
|
+
Here's how to set up the evaluator and resolve a simple expression.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { createExpressionEvaluator } from '@axi-engine/expressions';
|
|
34
|
+
import type { Expression } from '@axi-engine/expressions';
|
|
35
|
+
import type { DataSource } from '@axi-engine/utils';
|
|
36
|
+
|
|
37
|
+
// 1. Create the evaluator (it comes with all core handlers pre-registered)
|
|
38
|
+
const evaluator = createExpressionEvaluator();
|
|
39
|
+
|
|
40
|
+
// 2. Define a data source that provides the state
|
|
41
|
+
const myGameDataSource: DataSource = {
|
|
42
|
+
get: (path) => {
|
|
43
|
+
const state = new Map<string, any>([
|
|
44
|
+
['player.level', 10],
|
|
45
|
+
['player.class', 'mage'],
|
|
46
|
+
['gate.locked', true],
|
|
47
|
+
]);
|
|
48
|
+
return state.get(path.join('.'));
|
|
49
|
+
},
|
|
50
|
+
has: (path) => { /* ... */ }
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// 3. Define an expression, for example in a JSON file or directly in code
|
|
54
|
+
const canOpenGate: Expression = {
|
|
55
|
+
and: [
|
|
56
|
+
{
|
|
57
|
+
comparison: {
|
|
58
|
+
op: '>=',
|
|
59
|
+
left: { path: ['player', 'level'] },
|
|
60
|
+
right: { value: 5 }
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
comparison: {
|
|
65
|
+
op: '==',
|
|
66
|
+
left: { path: ['gate', 'locked'] },
|
|
67
|
+
right: { value: true }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// 4. Resolve the expression
|
|
74
|
+
async function checkCondition() {
|
|
75
|
+
const result = await evaluator.resolve(canOpenGate, myGameDataSource);
|
|
76
|
+
console.log('Can the player open the gate?', result); // -> true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
checkCondition();
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Built-in Expressions
|
|
83
|
+
|
|
84
|
+
Here are some examples of the core expression types available out of the box.
|
|
85
|
+
|
|
86
|
+
| Type | Example | Description |
|
|
87
|
+
|:-----------------|:----------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------|
|
|
88
|
+
| **`comparison`** | `{ "comparison": { "op": ">", "left": { "path": "p.hp" }, "right": { "value": 50 } } }` | Compares two values. |
|
|
89
|
+
| **`and`** | `{ "and": [ { ...expr1 }, { ...expr2 } ] }` | Returns `true` if all child expressions are true. |
|
|
90
|
+
| **`or`** | `{ "or": [ { ...expr1 }, { ...expr2 } ] }` | Returns `true` if at least one child expression is true. |
|
|
91
|
+
| **`not`** | `{ "not": { "exists": "p.curse" } }` | Inverts the result of a child expression. |
|
|
92
|
+
| **`exists`** | `{ "exists": "p.inventory.key" }` | Returns `true` if a value exists at the given path. |
|
|
93
|
+
| **`in`** | `{ "in": { "value": { "path": "p.class" }, "array": ["mage", "warlock"] } }` | Checks if a value is present in an array. The array can also be a reference: `"array": { "path": "q.valid_classes" }` |
|
|
94
|
+
| **`chance`** | `{ "chance": { "value": 15.5 } }` | Returns `true` based on a 15.5% probability. |
|
|
95
|
+
| **`literal`** | `{ "literal": true }` | Directly returns `true` or `false`. Useful for debugging. |
|
|
96
|
+
|
|
97
|
+
## Extending with Custom Expressions
|
|
98
|
+
|
|
99
|
+
Adding your own expression types is straightforward. Let's create a `between` expression.
|
|
100
|
+
|
|
101
|
+
**1. Define the Expression Type**
|
|
102
|
+
Create an interface for your new expression.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// my-expressions.ts
|
|
106
|
+
import type { Operand } from '@axi-engine/expressions';
|
|
107
|
+
|
|
108
|
+
export interface BetweenExpression {
|
|
109
|
+
between: {
|
|
110
|
+
value: Operand,
|
|
111
|
+
min: Operand,
|
|
112
|
+
max: Operand
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**2. Augment the Global Definitions**
|
|
118
|
+
|
|
119
|
+
Use TypeScript's declaration merging to make the evaluator aware of your new type.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// my-expressions.ts
|
|
123
|
+
import type { ExpressionDefinitions } from '@axi-engine/expressions';
|
|
124
|
+
|
|
125
|
+
declare module '@axi-engine/expressions' {
|
|
126
|
+
export interface ExpressionDefinitions {
|
|
127
|
+
between: BetweenExpression;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**3. Create the Handler**
|
|
133
|
+
|
|
134
|
+
Write the class that contains the evaluation logic.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// BetweenExpressionHandler.ts
|
|
138
|
+
import { ExpressionHandler, resolveOperandAsScalar } from '@axi-engine/expressions';
|
|
139
|
+
import { isNumber } from '@axi-engine/utils';
|
|
140
|
+
|
|
141
|
+
class BetweenExpressionHandler implements ExpressionHandler<BetweenExpression> {
|
|
142
|
+
type: 'between' = 'between';
|
|
143
|
+
|
|
144
|
+
async resolve(exp: BetweenExpression, context: ExpressionEvaluatorContext) {
|
|
145
|
+
const value = resolveOperandAsScalar(exp.between.value, context.source());
|
|
146
|
+
const min = resolveOperandAsScalar(exp.between.min, context.source());
|
|
147
|
+
const max = resolveOperandAsScalar(exp.between.max, context.source());
|
|
148
|
+
|
|
149
|
+
if (isNumber(value) && isNumber(min) && isNumber(max)) {
|
|
150
|
+
return value >= min && value <= max;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**4. Register the Handler**
|
|
158
|
+
|
|
159
|
+
Pass your new handler to the factory function during initialization.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { createExpressionEvaluator } from '@axi-engine/expressions';
|
|
163
|
+
|
|
164
|
+
const myHandlers = [new BetweenExpressionHandler()];
|
|
165
|
+
const evaluator = createExpressionEvaluator(myHandlers);
|
|
166
|
+
|
|
167
|
+
// Now you can use it!
|
|
168
|
+
const expression = {
|
|
169
|
+
between: { value: { path: 'player.level' }, min: { value: 10 }, max: { value: 20 } }
|
|
170
|
+
};
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## API Reference
|
|
174
|
+
|
|
175
|
+
[**Browse the API Documentation here**](https://github.com/axijs/engine/tree/main/packages/expressions/docs/api)
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { ScalarType, PathType, DataSource } from '@axi-engine/utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents an operand that is a direct, static value.
|
|
5
|
+
*/
|
|
6
|
+
type ValueOperand = {
|
|
7
|
+
value: ScalarType;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Represents an operand that is a reference to a value,
|
|
11
|
+
* which will be resolved from the context (scope) using a path.
|
|
12
|
+
*/
|
|
13
|
+
type ReferenceOperand = {
|
|
14
|
+
path: PathType;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Represents an operand that is a mathematical calculation.
|
|
18
|
+
* The result of this calculation is used as the operand's value.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Represents the expression: player.hp + 10
|
|
22
|
+
* {
|
|
23
|
+
* "arithmetic": {
|
|
24
|
+
* "op": "+",
|
|
25
|
+
* "left": { "path": "player.hp" },
|
|
26
|
+
* "right": { "value": 10 }
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
*/
|
|
30
|
+
type ArithmeticOperand = {
|
|
31
|
+
arithmetic: {
|
|
32
|
+
op: MathOperationType;
|
|
33
|
+
left: Operand;
|
|
34
|
+
right: Operand;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Represents an operand within a logical expression.
|
|
39
|
+
* It can be either a direct value (ValueOperand) or a reference to one (ReferenceOperand).
|
|
40
|
+
*/
|
|
41
|
+
type Operand = ValueOperand | ReferenceOperand | ArithmeticOperand;
|
|
42
|
+
/**
|
|
43
|
+
* Defines the set of allowed operators for a `ComparisonExpression`.
|
|
44
|
+
*/
|
|
45
|
+
type ComparisonOperationType = '<' | '>' | '<=' | '>=' | '==' | '!=';
|
|
46
|
+
/**
|
|
47
|
+
* Defines the set of allowed operators for an `ArithmeticOperand`.
|
|
48
|
+
*/
|
|
49
|
+
type MathOperationType = '+' | '-' | '*' | '/';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Type guard that checks if a value is a `ValueOperand`.
|
|
53
|
+
* A `ValueOperand` represents a direct, literal value.
|
|
54
|
+
*
|
|
55
|
+
* @param val The value to check.
|
|
56
|
+
* @returns {boolean} `true` if the value is a `ValueOperand`, otherwise `false`.
|
|
57
|
+
*/
|
|
58
|
+
declare function isValueOperand(val: unknown): val is ValueOperand;
|
|
59
|
+
/**
|
|
60
|
+
* Type guard that checks if a value is a `ReferenceOperand`.
|
|
61
|
+
* A `ReferenceOperand` represents a reference to a value via a path.
|
|
62
|
+
*
|
|
63
|
+
* @param val The value to check.
|
|
64
|
+
* @returns {boolean} `true` if the value is a `ReferenceOperand`, otherwise `false`.
|
|
65
|
+
*/
|
|
66
|
+
declare function isReferenceOperand(val: unknown): val is ReferenceOperand;
|
|
67
|
+
/**
|
|
68
|
+
* Type guard that checks if a value is an `ArithmeticOperand`.
|
|
69
|
+
* An `ArithmeticOperand` represents a mathematical calculation.
|
|
70
|
+
*
|
|
71
|
+
* @param val The value to check.
|
|
72
|
+
* @returns {boolean} `true` if the value is an `ArithmeticOperand`, otherwise `false`.
|
|
73
|
+
*/
|
|
74
|
+
declare function isArithmeticOperand(val: unknown): val is ArithmeticOperand;
|
|
75
|
+
/**
|
|
76
|
+
* Type guard that checks if a value is any valid `Operand` type.
|
|
77
|
+
*
|
|
78
|
+
* @param val The value to check.
|
|
79
|
+
* @returns {boolean} `true` if the value is a `ValueOperand`, `ReferenceOperand`,
|
|
80
|
+
* or `ArithmeticOperand`, otherwise `false`.
|
|
81
|
+
*/
|
|
82
|
+
declare function isOperand(val: unknown): val is Operand;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Represents an expression that resolves to a hardcoded boolean value.
|
|
86
|
+
* This is primarily useful for debugging, testing, or creating temporary stubs
|
|
87
|
+
* in a larger expression tree.
|
|
88
|
+
* @example
|
|
89
|
+
* { "literal": true }
|
|
90
|
+
*/
|
|
91
|
+
interface LiteralExpression {
|
|
92
|
+
literal: boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Represents a comparison between two operands.
|
|
96
|
+
* It evaluates the left and right operands and compares them using the specified operator.
|
|
97
|
+
* @example
|
|
98
|
+
* {
|
|
99
|
+
* "comparison": {
|
|
100
|
+
* "op": ">=",
|
|
101
|
+
* "left": { "path": "player.level" },
|
|
102
|
+
* "right": { "value": 10 }
|
|
103
|
+
* }
|
|
104
|
+
* }
|
|
105
|
+
*/
|
|
106
|
+
interface ComparisonExpression {
|
|
107
|
+
comparison: {
|
|
108
|
+
op: ComparisonOperationType;
|
|
109
|
+
left: Operand;
|
|
110
|
+
right: Operand;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Represents an expression that checks for the existence of a variable at a given path.
|
|
115
|
+
*/
|
|
116
|
+
interface ExistsExpression {
|
|
117
|
+
exists: PathType;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Represents a logical AND operation.
|
|
121
|
+
* It evaluates an array of child expressions and resolves to `true` only if
|
|
122
|
+
* *all* of them resolve to `true`.
|
|
123
|
+
* @example
|
|
124
|
+
* {
|
|
125
|
+
* "and": [
|
|
126
|
+
* { "exists": "player.key" },
|
|
127
|
+
* { "comparison": { "op": "==", "left": { "path": "gate.locked" }, "right": { "value": true } } }
|
|
128
|
+
* ]
|
|
129
|
+
* }
|
|
130
|
+
*/
|
|
131
|
+
interface AndExpression {
|
|
132
|
+
and: Expression[];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Represents a logical OR operation.
|
|
136
|
+
* It evaluates an array of child expressions and resolves to `true` if
|
|
137
|
+
* *at least one* of them resolves to `true`.
|
|
138
|
+
* @example
|
|
139
|
+
* {
|
|
140
|
+
* "or": [
|
|
141
|
+
* { "exists": "player.key" },
|
|
142
|
+
* { "exists": "player.gun" }
|
|
143
|
+
* ]
|
|
144
|
+
* }
|
|
145
|
+
*/
|
|
146
|
+
interface OrExpression {
|
|
147
|
+
or: Expression[];
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Represents a logical NOT operation.
|
|
151
|
+
* It evaluates a single child expression and inverts its boolean result.
|
|
152
|
+
* @example
|
|
153
|
+
* {
|
|
154
|
+
* "not": { "exists": "player.effects.poison" }
|
|
155
|
+
* }
|
|
156
|
+
*/
|
|
157
|
+
interface NotExpression {
|
|
158
|
+
not: Expression;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Represents a probabilistic expression that resolves to `true` or `false`
|
|
162
|
+
* based on a given chance.
|
|
163
|
+
* The operand should resolve to a number between 0 and 100. step 0.01
|
|
164
|
+
* @example
|
|
165
|
+
* // 15% chance to be true
|
|
166
|
+
* { "chance": { "value": 15 } }
|
|
167
|
+
* @example
|
|
168
|
+
* // Chance is determined by the player's luck stat
|
|
169
|
+
* { "chance": { "path": "player.stats.luck" } }
|
|
170
|
+
*/
|
|
171
|
+
interface ChanceExpression {
|
|
172
|
+
chance: Operand;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Represents an expression that checks if a value is present within an array.
|
|
176
|
+
* This is useful for checking against a list of possible values, such as statuses,
|
|
177
|
+
* factions, or item types.
|
|
178
|
+
* @example
|
|
179
|
+
* // Check if player's faction is one of the "evil" ones
|
|
180
|
+
* {
|
|
181
|
+
* "in": {
|
|
182
|
+
* "value": { "path": "player.faction" },
|
|
183
|
+
* "array": [ "orcs", "goblins", "undead" ]
|
|
184
|
+
* }
|
|
185
|
+
* }
|
|
186
|
+
* @example
|
|
187
|
+
* // Check against a dynamic array from the data source
|
|
188
|
+
* {
|
|
189
|
+
* "in": {
|
|
190
|
+
* "value": { "path": "player.class" },
|
|
191
|
+
* "array": { "path": "quest.valid_classes" }
|
|
192
|
+
* }
|
|
193
|
+
* }
|
|
194
|
+
*/
|
|
195
|
+
interface InExpression {
|
|
196
|
+
in: {
|
|
197
|
+
/**
|
|
198
|
+
* The operand whose resolved value will be searched for in the array.
|
|
199
|
+
* should be scalar type (string, number, boolean)
|
|
200
|
+
*/
|
|
201
|
+
value: Operand;
|
|
202
|
+
/**
|
|
203
|
+
* The collection to check against. This can be either:
|
|
204
|
+
* - An operand that resolves to an array.
|
|
205
|
+
* - A literal array defined directly in the expression.
|
|
206
|
+
*/
|
|
207
|
+
array: Operand | (Operand | ScalarType)[];
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* A map defining all available expression types by their unique key.
|
|
212
|
+
* This interface is designed to be augmented by third-party plugins
|
|
213
|
+
* using TypeScript's declaration merging.
|
|
214
|
+
*/
|
|
215
|
+
interface ExpressionDefinitions {
|
|
216
|
+
literal: LiteralExpression;
|
|
217
|
+
comparison: ComparisonExpression;
|
|
218
|
+
exists: ExistsExpression;
|
|
219
|
+
and: AndExpression;
|
|
220
|
+
or: OrExpression;
|
|
221
|
+
not: NotExpression;
|
|
222
|
+
chance: ChanceExpression;
|
|
223
|
+
in: InExpression;
|
|
224
|
+
}
|
|
225
|
+
/** The union of all possible expression objects. */
|
|
226
|
+
type Expression = ExpressionDefinitions[keyof ExpressionDefinitions];
|
|
227
|
+
/** The union of all possible expression names (keys). uses in ExpressionHandler */
|
|
228
|
+
type ExpressionName = keyof ExpressionDefinitions;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Recursively resolves an Operand into its final scalar value.
|
|
232
|
+
*
|
|
233
|
+
* This function processes different types of operands:
|
|
234
|
+
* - `ValueOperand`: Returns the direct value.
|
|
235
|
+
* - `ReferenceOperand`: Looks up the value from the provided data source using its path.
|
|
236
|
+
* - `ArithmeticOperand`: Recursively resolves its left and right sides and then performs the calculation.
|
|
237
|
+
*
|
|
238
|
+
* @param op The `Operand` object to resolve. This can be a direct value, a path reference, or a nested arithmetic operation.
|
|
239
|
+
* @param source The `DataSource` used to look up values for `ReferenceOperand` types.
|
|
240
|
+
* @returns unknown.
|
|
241
|
+
* @throws {Error} If a `ReferenceOperand` points to a path that does not resolve to a scalar value.
|
|
242
|
+
* @throws {Error} If an `ArithmeticOperand` is used with non-numeric values.
|
|
243
|
+
* @throws {Error} If an unknown or unsupported operand type is provided.
|
|
244
|
+
*/
|
|
245
|
+
declare function resolveOperand(op: Operand, source: DataSource): unknown;
|
|
246
|
+
/**
|
|
247
|
+
* Resolves an operand and asserts that the result is a `ScalarType`.
|
|
248
|
+
*
|
|
249
|
+
* This function acts as a type-safe convenience wrapper around the more generic
|
|
250
|
+
* `resolveOperand` function. It is the preferred way to resolve operands within
|
|
251
|
+
* expression handlers that are designed to work only with scalar values
|
|
252
|
+
* (string, number, or boolean), as it centralizes type checking.
|
|
253
|
+
*
|
|
254
|
+
* @param op The `Operand` object to resolve.
|
|
255
|
+
* @param source The `DataSource` used to look up values for reference operands.
|
|
256
|
+
* @returns The resolved scalar value.
|
|
257
|
+
* @throws {Error} If the resolved value from the operand is not a `ScalarType`
|
|
258
|
+
* (e.g., it's an object, array, or undefined).
|
|
259
|
+
*/
|
|
260
|
+
declare function resolveOperandAsScalar(op: Operand, source: DataSource): ScalarType;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Provides the execution context for an `ExpressionHandler`, giving it the tools
|
|
264
|
+
* needed to perform its evaluation. An instance of this context is passed to
|
|
265
|
+
* every handler's `resolve` method.
|
|
266
|
+
* @interface
|
|
267
|
+
*/
|
|
268
|
+
interface ExpressionEvaluatorContext {
|
|
269
|
+
/**
|
|
270
|
+
* A function to recursively resolve nested or child expressions.
|
|
271
|
+
* This is used by logical handlers like `AndExpressionHandler` or `NotExpressionHandler`
|
|
272
|
+
* to evaluate their child expressions using the main evaluator logic.
|
|
273
|
+
* @param expression The nested expression to resolve.
|
|
274
|
+
* @returns A promise that resolves to the boolean result of the nested expression.
|
|
275
|
+
*/
|
|
276
|
+
resolve(expression: Expression): Promise<boolean>;
|
|
277
|
+
/**
|
|
278
|
+
* A function that returns the `DataSource` for the current evaluation.
|
|
279
|
+
* This allows the handler to retrieve values needed for `ReferenceOperand`s.
|
|
280
|
+
* @returns The active `DataSource`.
|
|
281
|
+
*/
|
|
282
|
+
source(): DataSource;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* The class responsible for evaluating expression trees.
|
|
286
|
+
*
|
|
287
|
+
* It acts as an orchestrator that manages a registry of `ExpressionHandler`
|
|
288
|
+
* instances and delegates the evaluation of a specific expression to the
|
|
289
|
+
* appropriate handler.
|
|
290
|
+
*
|
|
291
|
+
* Users typically do not create this class directly but use the
|
|
292
|
+
* `createExpressionEvaluator` factory function, which provides a pre-configured
|
|
293
|
+
* instance with all core handlers.
|
|
294
|
+
*
|
|
295
|
+
* @class
|
|
296
|
+
*/
|
|
297
|
+
declare class ExpressionEvaluator {
|
|
298
|
+
/** @internal A map of registered expression handlers. */
|
|
299
|
+
handlers: Map<keyof ExpressionDefinitions, ExpressionHandler<Expression>>;
|
|
300
|
+
/**
|
|
301
|
+
* Registers a new `ExpressionHandler` with the evaluator.
|
|
302
|
+
* This is the primary mechanism for extending the expression language with
|
|
303
|
+
* custom logic and new expression types.
|
|
304
|
+
*
|
|
305
|
+
* @param handler The `ExpressionHandler` instance to register.
|
|
306
|
+
* @throws {Error} Throws an error if a handler for the same expression type
|
|
307
|
+
* is already registered.
|
|
308
|
+
*/
|
|
309
|
+
register(handler: ExpressionHandler): void;
|
|
310
|
+
/**
|
|
311
|
+
* Resolves a given expression against a data source.
|
|
312
|
+
*
|
|
313
|
+
* This is the main entry point for the evaluation process. It identifies the
|
|
314
|
+
* expression type by its key, finds the corresponding handler, creates the
|
|
315
|
+
* evaluation context, and delegates the evaluation task to the handler.
|
|
316
|
+
*
|
|
317
|
+
* @param expression The expression object to evaluate.
|
|
318
|
+
* @param data The `DataSource` to be used for resolving any `ReferenceOperand`s
|
|
319
|
+
* within the expression tree.
|
|
320
|
+
* @returns A promise that resolves to `true` or `false` based on the
|
|
321
|
+
* evaluation result.
|
|
322
|
+
*/
|
|
323
|
+
resolve(expression: Expression, data: DataSource): Promise<boolean>;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Defines the contract for a class that can evaluate a specific type of expression.
|
|
328
|
+
*
|
|
329
|
+
* Each expression type in the system (e.g., `comparison`, `and`, `in`) must have a
|
|
330
|
+
* corresponding class that implements this interface. The `ExpressionEvaluator` uses these
|
|
331
|
+
* handlers to delegate the actual evaluation logic.
|
|
332
|
+
*
|
|
333
|
+
* @interface
|
|
334
|
+
* @template T - The specific `Expression` subtype that this handler is responsible for.
|
|
335
|
+
* This provides strong typing within the `resolve` method.
|
|
336
|
+
*/
|
|
337
|
+
interface ExpressionHandler<T extends Expression = Expression> {
|
|
338
|
+
/**
|
|
339
|
+
* The unique key for the expression type this handler processes.
|
|
340
|
+
* This must match one of the keys in the `ExpressionDefinitions` interface.
|
|
341
|
+
*/
|
|
342
|
+
type: ExpressionName;
|
|
343
|
+
/**
|
|
344
|
+
* The core evaluation logic for the expression.
|
|
345
|
+
*
|
|
346
|
+
* @param exp The specific expression object to be evaluated, strongly typed to `T`.
|
|
347
|
+
* @param context The `ExpressionEvaluatorContext` which provides tools for the
|
|
348
|
+
* handler, such as a way to recursively resolve child expressions or access the
|
|
349
|
+
* data source.
|
|
350
|
+
* @returns {Promise<boolean>} A promise that resolves to the boolean result of the evaluation.
|
|
351
|
+
*/
|
|
352
|
+
resolve(exp: T, context: ExpressionEvaluatorContext): Promise<boolean>;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
declare class AndExpressionHandler implements ExpressionHandler<AndExpression> {
|
|
356
|
+
type: ExpressionName;
|
|
357
|
+
resolve(exp: AndExpression, context: ExpressionEvaluatorContext): Promise<boolean>;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* An expression handler for the `chance` expression.
|
|
362
|
+
*
|
|
363
|
+
* This handler evaluates to `true` or `false` based on a probabilistic check.
|
|
364
|
+
* It resolves its operand to a numeric percentage value and compares it against
|
|
365
|
+
* a random number roll.
|
|
366
|
+
*/
|
|
367
|
+
declare class ChanceExpressionHandler implements ExpressionHandler<ChanceExpression> {
|
|
368
|
+
type: ExpressionName;
|
|
369
|
+
/**
|
|
370
|
+
* Resolves the `chance` expression.
|
|
371
|
+
*
|
|
372
|
+
* The method first resolves the operand to a scalar value. It supports both
|
|
373
|
+
* numbers (e.g., `50`) and strings (e.g., `"50"`, `"50%"`), which are parsed
|
|
374
|
+
* into a numeric percentage. It then generates a random integer from 0 to 99
|
|
375
|
+
* and returns `true` if this random number is less than the resolved chance value.
|
|
376
|
+
*
|
|
377
|
+
* @param exp The `ChanceExpression` object to resolve.
|
|
378
|
+
* @param context The context for the expression evaluation, providing access to the data source.
|
|
379
|
+
* @returns {Promise<boolean>} A promise that resolves to `true` if the random roll
|
|
380
|
+
* succeeds, and `false` otherwise.
|
|
381
|
+
* @throws {Error} If the operand resolves to a value that cannot be parsed into
|
|
382
|
+
* a number (e.g., a boolean or a non-numeric string).
|
|
383
|
+
*/
|
|
384
|
+
resolve(exp: ChanceExpression, context: ExpressionEvaluatorContext): Promise<boolean>;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
declare class ComparisonExpressionHandler implements ExpressionHandler<ComparisonExpression> {
|
|
388
|
+
type: ExpressionName;
|
|
389
|
+
resolve(exp: ComparisonExpression, context: ExpressionEvaluatorContext): Promise<boolean>;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
declare class ExistsExpressionHandler implements ExpressionHandler<ExistsExpression> {
|
|
393
|
+
type: ExpressionName;
|
|
394
|
+
resolve(exp: ExistsExpression, context: ExpressionEvaluatorContext): Promise<boolean>;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* An expression handler for the `in` expression.
|
|
399
|
+
*
|
|
400
|
+
* This handler checks if a resolved scalar value is present within a collection (array).
|
|
401
|
+
* It supports both literal arrays defined directly in the expression and arrays
|
|
402
|
+
* resolved from a data source via an operand.
|
|
403
|
+
*/
|
|
404
|
+
declare class InExpressionHandler implements ExpressionHandler<InExpression> {
|
|
405
|
+
type: ExpressionName;
|
|
406
|
+
/**
|
|
407
|
+
* Resolves the `in` expression.
|
|
408
|
+
*
|
|
409
|
+
* The method performs the following steps:
|
|
410
|
+
* 1. Resolves the `value` operand to a scalar.
|
|
411
|
+
* 2. Obtains the source array, which can be a literal array from the expression
|
|
412
|
+
* or the result of resolving the `array` operand.
|
|
413
|
+
* 3. Ensures the source is a valid array.
|
|
414
|
+
* 4. Resolves every item within the source array to a scalar value.
|
|
415
|
+
* 5. Checks if the value from step 1 is included in the resolved array from step 4.
|
|
416
|
+
*
|
|
417
|
+
* @param exp The `InExpression` object to resolve.
|
|
418
|
+
* @param context The context for the expression evaluation, providing the data source.
|
|
419
|
+
* @returns {Promise<boolean>} A promise that resolves to `true` if the value is found
|
|
420
|
+
* in the array, and `false` otherwise.
|
|
421
|
+
* @throws {Error} If the source for the array does not resolve to an array.
|
|
422
|
+
* @throws {Error} If any operand within the process fails to resolve correctly.
|
|
423
|
+
*/
|
|
424
|
+
resolve(exp: InExpression, context: ExpressionEvaluatorContext): Promise<boolean>;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
declare class LiteralExpressionHandler implements ExpressionHandler<LiteralExpression> {
|
|
428
|
+
type: ExpressionName;
|
|
429
|
+
resolve(exp: LiteralExpression, _context: ExpressionEvaluatorContext): Promise<boolean>;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
declare class NotExpressionHandler implements ExpressionHandler<NotExpression> {
|
|
433
|
+
type: ExpressionName;
|
|
434
|
+
resolve(exp: NotExpression, context: ExpressionEvaluatorContext): Promise<boolean>;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
declare class OrExpressionHandler implements ExpressionHandler<OrExpression> {
|
|
438
|
+
type: ExpressionName;
|
|
439
|
+
resolve(exp: OrExpression, context: ExpressionEvaluatorContext): Promise<boolean>;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* A factory function that creates and initializes an `ExpressionEvaluator` instance.
|
|
444
|
+
*
|
|
445
|
+
* This is the recommended way to set up the evaluator, as it comes pre-configured
|
|
446
|
+
* with handlers for all core expression types (logical, comparison, chance, etc.).
|
|
447
|
+
* It also provides a simple way to extend the evaluator with custom logic by passing
|
|
448
|
+
* additional handlers.
|
|
449
|
+
*
|
|
450
|
+
* @param {ExpressionHandler[]} [additionalHandlers] - An optional array of custom
|
|
451
|
+
* `ExpressionHandler` instances to register in addition to the core ones. This allows for
|
|
452
|
+
* extending the expression language with new capabilities.
|
|
453
|
+
* @returns {ExpressionEvaluator} A fully configured `ExpressionEvaluator` instance,
|
|
454
|
+
* ready for resolving expressions.
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* // Basic setup with only core handlers
|
|
458
|
+
* const coreEvaluator = createExpressionEvaluator();
|
|
459
|
+
* const result = await coreEvaluator.resolve(someExpression, dataSource);
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* // Setup with a custom handler for a new expression type
|
|
463
|
+
* const customHandlers = [new MyCustomExpressionHandler()];
|
|
464
|
+
* const extendedEvaluator = createExpressionEvaluator(customHandlers);
|
|
465
|
+
* const customResult = await extendedEvaluator.resolve(myCustomExpression, dataSource);
|
|
466
|
+
*/
|
|
467
|
+
declare function createExpressionEvaluator(additionalHandlers?: ExpressionHandler[]): ExpressionEvaluator;
|
|
468
|
+
|
|
469
|
+
export { type AndExpression, AndExpressionHandler, type ArithmeticOperand, type ChanceExpression, ChanceExpressionHandler, type ComparisonExpression, ComparisonExpressionHandler, type ComparisonOperationType, type ExistsExpression, ExistsExpressionHandler, type Expression, type ExpressionDefinitions, ExpressionEvaluator, type ExpressionEvaluatorContext, type ExpressionHandler, type ExpressionName, type InExpression, InExpressionHandler, type LiteralExpression, LiteralExpressionHandler, type MathOperationType, type NotExpression, NotExpressionHandler, type Operand, type OrExpression, OrExpressionHandler, type ReferenceOperand, type ValueOperand, createExpressionEvaluator, isArithmeticOperand, isOperand, isReferenceOperand, isValueOperand, resolveOperand, resolveOperandAsScalar };
|