@the-trybe/formula-engine 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/.claude/settings.local.json +6 -0
- package/PRD_FORMULA_ENGINE.md +1863 -0
- package/README.md +382 -0
- package/dist/decimal-utils.d.ts +180 -0
- package/dist/decimal-utils.js +355 -0
- package/dist/dependency-extractor.d.ts +20 -0
- package/dist/dependency-extractor.js +103 -0
- package/dist/dependency-graph.d.ts +60 -0
- package/dist/dependency-graph.js +252 -0
- package/dist/errors.d.ts +161 -0
- package/dist/errors.js +260 -0
- package/dist/evaluator.d.ts +51 -0
- package/dist/evaluator.js +494 -0
- package/dist/formula-engine.d.ts +79 -0
- package/dist/formula-engine.js +355 -0
- package/dist/functions.d.ts +3 -0
- package/dist/functions.js +720 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +61 -0
- package/dist/lexer.d.ts +25 -0
- package/dist/lexer.js +357 -0
- package/dist/parser.d.ts +32 -0
- package/dist/parser.js +372 -0
- package/dist/types.d.ts +228 -0
- package/dist/types.js +62 -0
- package/jest.config.js +23 -0
- package/package.json +35 -0
- package/src/decimal-utils.ts +408 -0
- package/src/dependency-extractor.ts +117 -0
- package/src/dependency-graph.test.ts +238 -0
- package/src/dependency-graph.ts +288 -0
- package/src/errors.ts +296 -0
- package/src/evaluator.ts +604 -0
- package/src/formula-engine.test.ts +660 -0
- package/src/formula-engine.ts +430 -0
- package/src/functions.ts +770 -0
- package/src/index.ts +103 -0
- package/src/lexer.test.ts +288 -0
- package/src/lexer.ts +394 -0
- package/src/parser.test.ts +349 -0
- package/src/parser.ts +449 -0
- package/src/types.ts +347 -0
- package/tsconfig.json +29 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { Decimal } from 'decimal.js';
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Value Types
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
export type ValueType = 'number' | 'decimal' | 'string' | 'boolean' | 'array' | 'object' | 'null' | 'any';
|
|
8
|
+
|
|
9
|
+
export type FormulaValue = Decimal | number | string | boolean | null | FormulaValue[] | { [key: string]: FormulaValue };
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Decimal Configuration
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
export enum DecimalRoundingMode {
|
|
16
|
+
CEIL = 'CEIL',
|
|
17
|
+
FLOOR = 'FLOOR',
|
|
18
|
+
DOWN = 'DOWN',
|
|
19
|
+
UP = 'UP',
|
|
20
|
+
HALF_UP = 'HALF_UP',
|
|
21
|
+
HALF_DOWN = 'HALF_DOWN',
|
|
22
|
+
HALF_EVEN = 'HALF_EVEN',
|
|
23
|
+
HALF_ODD = 'HALF_ODD',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DecimalConfig {
|
|
27
|
+
precision?: number;
|
|
28
|
+
roundingMode?: DecimalRoundingMode;
|
|
29
|
+
divisionScale?: number;
|
|
30
|
+
preserveTrailingZeros?: boolean;
|
|
31
|
+
autoConvertFloats?: boolean;
|
|
32
|
+
maxExponent?: number;
|
|
33
|
+
minExponent?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Rounding Configuration
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
export interface RoundingConfig {
|
|
41
|
+
mode: 'HALF_UP' | 'HALF_DOWN' | 'FLOOR' | 'CEIL' | 'NONE';
|
|
42
|
+
precision: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Error Behavior
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
export interface ErrorBehavior {
|
|
50
|
+
type: 'THROW' | 'NULL' | 'ZERO' | 'DEFAULT' | 'SKIP';
|
|
51
|
+
defaultValue?: unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Formula Definition
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
export interface FormulaDefinition {
|
|
59
|
+
id: string;
|
|
60
|
+
expression: string;
|
|
61
|
+
dependencies?: string[];
|
|
62
|
+
onError?: ErrorBehavior;
|
|
63
|
+
defaultValue?: unknown;
|
|
64
|
+
rounding?: RoundingConfig;
|
|
65
|
+
metadata?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Evaluation Context
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
export interface EvaluationContext {
|
|
73
|
+
variables: Record<string, unknown>;
|
|
74
|
+
collections?: Record<string, unknown[]>;
|
|
75
|
+
extra?: Record<string, unknown>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Engine Configuration
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
export interface OperatorDefinition {
|
|
83
|
+
symbol: string;
|
|
84
|
+
precedence: number;
|
|
85
|
+
associativity: 'left' | 'right';
|
|
86
|
+
handler: (left: unknown, right: unknown) => unknown;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface SecurityConfig {
|
|
90
|
+
maxExpressionLength?: number;
|
|
91
|
+
maxRecursionDepth?: number;
|
|
92
|
+
maxIterations?: number;
|
|
93
|
+
maxExecutionTime?: number;
|
|
94
|
+
allowedFunctions?: string[];
|
|
95
|
+
blockedFunctions?: string[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface FormulaEngineConfig {
|
|
99
|
+
enableCache?: boolean;
|
|
100
|
+
maxCacheSize?: number;
|
|
101
|
+
defaultErrorBehavior?: ErrorBehavior;
|
|
102
|
+
defaultRounding?: RoundingConfig;
|
|
103
|
+
variablePrefix?: string;
|
|
104
|
+
contextPrefix?: string;
|
|
105
|
+
strictMode?: boolean;
|
|
106
|
+
operators?: OperatorDefinition[];
|
|
107
|
+
functions?: FunctionDefinition[];
|
|
108
|
+
decimal?: DecimalConfig;
|
|
109
|
+
security?: SecurityConfig;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// AST Node Types
|
|
114
|
+
// ============================================================================
|
|
115
|
+
|
|
116
|
+
export interface DecimalLiteral {
|
|
117
|
+
type: 'DecimalLiteral';
|
|
118
|
+
value: string;
|
|
119
|
+
raw: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface NumberLiteral {
|
|
123
|
+
type: 'NumberLiteral';
|
|
124
|
+
value: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface StringLiteral {
|
|
128
|
+
type: 'StringLiteral';
|
|
129
|
+
value: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface BooleanLiteral {
|
|
133
|
+
type: 'BooleanLiteral';
|
|
134
|
+
value: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface NullLiteral {
|
|
138
|
+
type: 'NullLiteral';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface ArrayLiteral {
|
|
142
|
+
type: 'ArrayLiteral';
|
|
143
|
+
elements: ASTNode[];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface VariableReference {
|
|
147
|
+
type: 'VariableReference';
|
|
148
|
+
prefix: '$' | '@';
|
|
149
|
+
name: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface BinaryOperation {
|
|
153
|
+
type: 'BinaryOperation';
|
|
154
|
+
operator: string;
|
|
155
|
+
left: ASTNode;
|
|
156
|
+
right: ASTNode;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface UnaryOperation {
|
|
160
|
+
type: 'UnaryOperation';
|
|
161
|
+
operator: string;
|
|
162
|
+
operand: ASTNode;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface ConditionalExpression {
|
|
166
|
+
type: 'ConditionalExpression';
|
|
167
|
+
condition: ASTNode;
|
|
168
|
+
consequent: ASTNode;
|
|
169
|
+
alternate: ASTNode;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface FunctionCall {
|
|
173
|
+
type: 'FunctionCall';
|
|
174
|
+
name: string;
|
|
175
|
+
arguments: ASTNode[];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface MemberAccess {
|
|
179
|
+
type: 'MemberAccess';
|
|
180
|
+
object: ASTNode;
|
|
181
|
+
property: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface IndexAccess {
|
|
185
|
+
type: 'IndexAccess';
|
|
186
|
+
object: ASTNode;
|
|
187
|
+
index: ASTNode;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export type ASTNode =
|
|
191
|
+
| DecimalLiteral
|
|
192
|
+
| NumberLiteral
|
|
193
|
+
| StringLiteral
|
|
194
|
+
| BooleanLiteral
|
|
195
|
+
| NullLiteral
|
|
196
|
+
| ArrayLiteral
|
|
197
|
+
| VariableReference
|
|
198
|
+
| BinaryOperation
|
|
199
|
+
| UnaryOperation
|
|
200
|
+
| ConditionalExpression
|
|
201
|
+
| FunctionCall
|
|
202
|
+
| MemberAccess
|
|
203
|
+
| IndexAccess;
|
|
204
|
+
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// Function Definition
|
|
207
|
+
// ============================================================================
|
|
208
|
+
|
|
209
|
+
export interface ArgumentType {
|
|
210
|
+
name: string;
|
|
211
|
+
type: ValueType;
|
|
212
|
+
required: boolean;
|
|
213
|
+
default?: unknown;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export type FunctionImplementation = (
|
|
217
|
+
args: unknown[],
|
|
218
|
+
context: EvaluationContext,
|
|
219
|
+
engine: unknown
|
|
220
|
+
) => unknown;
|
|
221
|
+
|
|
222
|
+
export interface FunctionDefinition {
|
|
223
|
+
name: string;
|
|
224
|
+
minArgs: number;
|
|
225
|
+
maxArgs: number;
|
|
226
|
+
argTypes?: ArgumentType[];
|
|
227
|
+
returnType: ValueType;
|
|
228
|
+
implementation: FunctionImplementation;
|
|
229
|
+
description?: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ============================================================================
|
|
233
|
+
// Dependency Graph
|
|
234
|
+
// ============================================================================
|
|
235
|
+
|
|
236
|
+
export interface DependencyGraph {
|
|
237
|
+
nodes: Set<string>;
|
|
238
|
+
edges: Map<string, Set<string>>;
|
|
239
|
+
hasCycles(): boolean;
|
|
240
|
+
getRoots(): Set<string>;
|
|
241
|
+
getDependents(nodeId: string): Set<string>;
|
|
242
|
+
getDependencies(nodeId: string): Set<string>;
|
|
243
|
+
getTransitiveDependencies(nodeId: string): Set<string>;
|
|
244
|
+
topologicalSort(): string[];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// Evaluation Results
|
|
249
|
+
// ============================================================================
|
|
250
|
+
|
|
251
|
+
export interface EvaluationResult {
|
|
252
|
+
value: unknown;
|
|
253
|
+
success: boolean;
|
|
254
|
+
error?: Error;
|
|
255
|
+
executionTimeMs: number;
|
|
256
|
+
accessedVariables: Set<string>;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface EvaluationResultSet {
|
|
260
|
+
results: Map<string, EvaluationResult>;
|
|
261
|
+
success: boolean;
|
|
262
|
+
errors: Error[];
|
|
263
|
+
totalExecutionTimeMs: number;
|
|
264
|
+
evaluationOrder: string[];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// Validation Result
|
|
269
|
+
// ============================================================================
|
|
270
|
+
|
|
271
|
+
export interface ValidationResult {
|
|
272
|
+
valid: boolean;
|
|
273
|
+
errors: Error[];
|
|
274
|
+
warnings: string[];
|
|
275
|
+
dependencyGraph: DependencyGraph;
|
|
276
|
+
evaluationOrder: string[];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// Cache Statistics
|
|
281
|
+
// ============================================================================
|
|
282
|
+
|
|
283
|
+
export interface CacheStats {
|
|
284
|
+
size: number;
|
|
285
|
+
hits: number;
|
|
286
|
+
misses: number;
|
|
287
|
+
hitRate: number;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ============================================================================
|
|
291
|
+
// Token Types
|
|
292
|
+
// ============================================================================
|
|
293
|
+
|
|
294
|
+
export enum TokenType {
|
|
295
|
+
// Literals
|
|
296
|
+
NUMBER = 'NUMBER',
|
|
297
|
+
STRING = 'STRING',
|
|
298
|
+
BOOLEAN = 'BOOLEAN',
|
|
299
|
+
NULL = 'NULL',
|
|
300
|
+
|
|
301
|
+
// Identifiers and Variables
|
|
302
|
+
IDENTIFIER = 'IDENTIFIER',
|
|
303
|
+
VARIABLE = 'VARIABLE',
|
|
304
|
+
CONTEXT_VAR = 'CONTEXT_VAR',
|
|
305
|
+
|
|
306
|
+
// Operators
|
|
307
|
+
PLUS = 'PLUS',
|
|
308
|
+
MINUS = 'MINUS',
|
|
309
|
+
MULTIPLY = 'MULTIPLY',
|
|
310
|
+
DIVIDE = 'DIVIDE',
|
|
311
|
+
MODULO = 'MODULO',
|
|
312
|
+
POWER = 'POWER',
|
|
313
|
+
|
|
314
|
+
// Comparison
|
|
315
|
+
EQ = 'EQ',
|
|
316
|
+
NEQ = 'NEQ',
|
|
317
|
+
LT = 'LT',
|
|
318
|
+
GT = 'GT',
|
|
319
|
+
LTE = 'LTE',
|
|
320
|
+
GTE = 'GTE',
|
|
321
|
+
|
|
322
|
+
// Logical
|
|
323
|
+
AND = 'AND',
|
|
324
|
+
OR = 'OR',
|
|
325
|
+
NOT = 'NOT',
|
|
326
|
+
|
|
327
|
+
// Punctuation
|
|
328
|
+
LPAREN = 'LPAREN',
|
|
329
|
+
RPAREN = 'RPAREN',
|
|
330
|
+
LBRACKET = 'LBRACKET',
|
|
331
|
+
RBRACKET = 'RBRACKET',
|
|
332
|
+
COMMA = 'COMMA',
|
|
333
|
+
DOT = 'DOT',
|
|
334
|
+
QUESTION = 'QUESTION',
|
|
335
|
+
COLON = 'COLON',
|
|
336
|
+
|
|
337
|
+
// Special
|
|
338
|
+
EOF = 'EOF',
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export interface Token {
|
|
342
|
+
type: TokenType;
|
|
343
|
+
value: string | number | boolean | null;
|
|
344
|
+
position: number;
|
|
345
|
+
line: number;
|
|
346
|
+
column: number;
|
|
347
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noImplicitAny": true,
|
|
9
|
+
"strictNullChecks": true,
|
|
10
|
+
"noImplicitThis": true,
|
|
11
|
+
"alwaysStrict": true,
|
|
12
|
+
"noUnusedLocals": false,
|
|
13
|
+
"noUnusedParameters": false,
|
|
14
|
+
"noImplicitReturns": true,
|
|
15
|
+
"noFallthroughCasesInSwitch": true,
|
|
16
|
+
"inlineSourceMap": true,
|
|
17
|
+
"inlineSources": true,
|
|
18
|
+
"experimentalDecorators": true,
|
|
19
|
+
"emitDecoratorMetadata": true,
|
|
20
|
+
"outDir": "./dist",
|
|
21
|
+
"rootDir": "./src",
|
|
22
|
+
"skipLibCheck": true,
|
|
23
|
+
"esModuleInterop": true,
|
|
24
|
+
"resolveJsonModule": true,
|
|
25
|
+
"moduleResolution": "node"
|
|
26
|
+
},
|
|
27
|
+
"include": ["src/**/*"],
|
|
28
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
29
|
+
}
|