@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.
Files changed (43) hide show
  1. package/.claude/settings.local.json +6 -0
  2. package/PRD_FORMULA_ENGINE.md +1863 -0
  3. package/README.md +382 -0
  4. package/dist/decimal-utils.d.ts +180 -0
  5. package/dist/decimal-utils.js +355 -0
  6. package/dist/dependency-extractor.d.ts +20 -0
  7. package/dist/dependency-extractor.js +103 -0
  8. package/dist/dependency-graph.d.ts +60 -0
  9. package/dist/dependency-graph.js +252 -0
  10. package/dist/errors.d.ts +161 -0
  11. package/dist/errors.js +260 -0
  12. package/dist/evaluator.d.ts +51 -0
  13. package/dist/evaluator.js +494 -0
  14. package/dist/formula-engine.d.ts +79 -0
  15. package/dist/formula-engine.js +355 -0
  16. package/dist/functions.d.ts +3 -0
  17. package/dist/functions.js +720 -0
  18. package/dist/index.d.ts +10 -0
  19. package/dist/index.js +61 -0
  20. package/dist/lexer.d.ts +25 -0
  21. package/dist/lexer.js +357 -0
  22. package/dist/parser.d.ts +32 -0
  23. package/dist/parser.js +372 -0
  24. package/dist/types.d.ts +228 -0
  25. package/dist/types.js +62 -0
  26. package/jest.config.js +23 -0
  27. package/package.json +35 -0
  28. package/src/decimal-utils.ts +408 -0
  29. package/src/dependency-extractor.ts +117 -0
  30. package/src/dependency-graph.test.ts +238 -0
  31. package/src/dependency-graph.ts +288 -0
  32. package/src/errors.ts +296 -0
  33. package/src/evaluator.ts +604 -0
  34. package/src/formula-engine.test.ts +660 -0
  35. package/src/formula-engine.ts +430 -0
  36. package/src/functions.ts +770 -0
  37. package/src/index.ts +103 -0
  38. package/src/lexer.test.ts +288 -0
  39. package/src/lexer.ts +394 -0
  40. package/src/parser.test.ts +349 -0
  41. package/src/parser.ts +449 -0
  42. package/src/types.ts +347 -0
  43. package/tsconfig.json +29 -0
package/src/errors.ts ADDED
@@ -0,0 +1,296 @@
1
+ import { ValueType } from './types';
2
+
3
+ export type ErrorCategory = 'PARSE' | 'VALIDATION' | 'EVALUATION' | 'CONFIGURATION';
4
+
5
+ export abstract class FormulaEngineError extends Error {
6
+ abstract readonly code: string;
7
+ abstract readonly category: ErrorCategory;
8
+
9
+ constructor(message: string) {
10
+ super(message);
11
+ this.name = this.constructor.name;
12
+ Object.setPrototypeOf(this, new.target.prototype);
13
+ }
14
+ }
15
+
16
+ // ============================================================================
17
+ // Parse Errors
18
+ // ============================================================================
19
+
20
+ export class SyntaxError extends FormulaEngineError {
21
+ readonly code = 'PARSE_SYNTAX_ERROR';
22
+ readonly category: ErrorCategory = 'PARSE';
23
+
24
+ constructor(
25
+ message: string,
26
+ public position: number,
27
+ public line: number,
28
+ public column: number,
29
+ public expression: string
30
+ ) {
31
+ super(`${message} at line ${line}, column ${column}`);
32
+ }
33
+ }
34
+
35
+ export class UnexpectedTokenError extends FormulaEngineError {
36
+ readonly code = 'PARSE_UNEXPECTED_TOKEN';
37
+ readonly category: ErrorCategory = 'PARSE';
38
+
39
+ constructor(
40
+ public token: string,
41
+ public expected: string[],
42
+ public position: number
43
+ ) {
44
+ super(`Unexpected token '${token}', expected one of: ${expected.join(', ')}`);
45
+ }
46
+ }
47
+
48
+ export class UnterminatedStringError extends FormulaEngineError {
49
+ readonly code = 'PARSE_UNTERMINATED_STRING';
50
+ readonly category: ErrorCategory = 'PARSE';
51
+
52
+ constructor(position: number) {
53
+ super(`Unterminated string starting at position ${position}`);
54
+ }
55
+ }
56
+
57
+ export class InvalidNumberError extends FormulaEngineError {
58
+ readonly code = 'PARSE_INVALID_NUMBER';
59
+ readonly category: ErrorCategory = 'PARSE';
60
+
61
+ constructor(value: string, position: number) {
62
+ super(`Invalid number '${value}' at position ${position}`);
63
+ }
64
+ }
65
+
66
+ // ============================================================================
67
+ // Validation Errors
68
+ // ============================================================================
69
+
70
+ export class CircularDependencyError extends FormulaEngineError {
71
+ readonly code = 'VALIDATION_CIRCULAR_DEPENDENCY';
72
+ readonly category: ErrorCategory = 'VALIDATION';
73
+
74
+ constructor(
75
+ public cycle: string[],
76
+ public involvedFormulas: string[]
77
+ ) {
78
+ super(`Circular dependency detected: ${cycle.join(' → ')}`);
79
+ }
80
+ }
81
+
82
+ export class UndefinedVariableError extends FormulaEngineError {
83
+ readonly code = 'VALIDATION_UNDEFINED_VARIABLE';
84
+ readonly category: ErrorCategory = 'VALIDATION';
85
+
86
+ constructor(
87
+ public variableName: string,
88
+ public expression: string
89
+ ) {
90
+ super(`Undefined variable: ${variableName}`);
91
+ }
92
+ }
93
+
94
+ export class UndefinedFunctionError extends FormulaEngineError {
95
+ readonly code = 'VALIDATION_UNDEFINED_FUNCTION';
96
+ readonly category: ErrorCategory = 'VALIDATION';
97
+
98
+ constructor(public functionName: string) {
99
+ super(`Undefined function: ${functionName}`);
100
+ }
101
+ }
102
+
103
+ export class DuplicateFormulaError extends FormulaEngineError {
104
+ readonly code = 'VALIDATION_DUPLICATE_FORMULA';
105
+ readonly category: ErrorCategory = 'VALIDATION';
106
+
107
+ constructor(public formulaId: string) {
108
+ super(`Duplicate formula ID: ${formulaId}`);
109
+ }
110
+ }
111
+
112
+ // ============================================================================
113
+ // Evaluation Errors
114
+ // ============================================================================
115
+
116
+ export class DivisionByZeroError extends FormulaEngineError {
117
+ readonly code = 'EVAL_DIVISION_BY_ZERO';
118
+ readonly category: ErrorCategory = 'EVALUATION';
119
+
120
+ constructor() {
121
+ super('Division by zero');
122
+ }
123
+ }
124
+
125
+ export class TypeMismatchError extends FormulaEngineError {
126
+ readonly code = 'EVAL_TYPE_MISMATCH';
127
+ readonly category: ErrorCategory = 'EVALUATION';
128
+
129
+ constructor(
130
+ public expected: ValueType | ValueType[] | string,
131
+ public actual: ValueType | string,
132
+ public context: string
133
+ ) {
134
+ const expectedStr = Array.isArray(expected) ? expected.join(' or ') : expected;
135
+ super(`Type mismatch: expected ${expectedStr}, got ${actual} in ${context}`);
136
+ }
137
+ }
138
+
139
+ export class ArgumentCountError extends FormulaEngineError {
140
+ readonly code = 'EVAL_ARGUMENT_COUNT';
141
+ readonly category: ErrorCategory = 'EVALUATION';
142
+
143
+ constructor(
144
+ public functionName: string,
145
+ public expected: { min: number; max: number },
146
+ public actual: number
147
+ ) {
148
+ const expectedStr = expected.min === expected.max
149
+ ? `${expected.min}`
150
+ : expected.max === -1
151
+ ? `at least ${expected.min}`
152
+ : `${expected.min}-${expected.max}`;
153
+ super(`Function ${functionName} expects ${expectedStr} arguments, got ${actual}`);
154
+ }
155
+ }
156
+
157
+ export class InvalidOperationError extends FormulaEngineError {
158
+ readonly code = 'EVAL_INVALID_OPERATION';
159
+ readonly category: ErrorCategory = 'EVALUATION';
160
+
161
+ constructor(
162
+ public operator: string,
163
+ public operandTypes: string[]
164
+ ) {
165
+ super(`Cannot apply operator '${operator}' to types: ${operandTypes.join(', ')}`);
166
+ }
167
+ }
168
+
169
+ export class PropertyAccessError extends FormulaEngineError {
170
+ readonly code = 'EVAL_PROPERTY_ACCESS';
171
+ readonly category: ErrorCategory = 'EVALUATION';
172
+
173
+ constructor(
174
+ public property: string,
175
+ public objectType: string
176
+ ) {
177
+ super(`Cannot access property '${property}' on ${objectType}`);
178
+ }
179
+ }
180
+
181
+ export class IndexAccessError extends FormulaEngineError {
182
+ readonly code = 'EVAL_INDEX_ACCESS';
183
+ readonly category: ErrorCategory = 'EVALUATION';
184
+
185
+ constructor(
186
+ public index: unknown,
187
+ public objectType: string
188
+ ) {
189
+ super(`Cannot use index '${index}' on ${objectType}`);
190
+ }
191
+ }
192
+
193
+ // ============================================================================
194
+ // Decimal Errors
195
+ // ============================================================================
196
+
197
+ export class DecimalError extends FormulaEngineError {
198
+ readonly code: string = 'DECIMAL_ERROR';
199
+ readonly category: ErrorCategory = 'EVALUATION';
200
+
201
+ constructor(message: string) {
202
+ super(message);
203
+ }
204
+ }
205
+
206
+ export class DecimalOverflowError extends DecimalError {
207
+ override readonly code: string = 'DECIMAL_OVERFLOW';
208
+
209
+ constructor(public value: string, public maxExponent: number) {
210
+ super(`Decimal overflow: exponent exceeds ${maxExponent}`);
211
+ }
212
+ }
213
+
214
+ export class DecimalUnderflowError extends DecimalError {
215
+ override readonly code: string = 'DECIMAL_UNDERFLOW';
216
+
217
+ constructor(public value: string, public minExponent: number) {
218
+ super(`Decimal underflow: exponent below ${minExponent}`);
219
+ }
220
+ }
221
+
222
+ export class DecimalDivisionByZeroError extends DecimalError {
223
+ override readonly code: string = 'DECIMAL_DIVISION_BY_ZERO';
224
+
225
+ constructor() {
226
+ super('Division by zero');
227
+ }
228
+ }
229
+
230
+ export class InvalidDecimalError extends DecimalError {
231
+ override readonly code: string = 'INVALID_DECIMAL';
232
+
233
+ constructor(public input: string) {
234
+ super(`Invalid decimal value: "${input}"`);
235
+ }
236
+ }
237
+
238
+ // ============================================================================
239
+ // Configuration Errors
240
+ // ============================================================================
241
+
242
+ export class ConfigurationError extends FormulaEngineError {
243
+ readonly code = 'CONFIGURATION_ERROR';
244
+ readonly category: ErrorCategory = 'CONFIGURATION';
245
+
246
+ constructor(message: string) {
247
+ super(message);
248
+ }
249
+ }
250
+
251
+ // ============================================================================
252
+ // Security Errors
253
+ // ============================================================================
254
+
255
+ export class SecurityError extends FormulaEngineError {
256
+ readonly code: string = 'SECURITY_ERROR';
257
+ readonly category: ErrorCategory = 'EVALUATION';
258
+
259
+ constructor(message: string) {
260
+ super(message);
261
+ }
262
+ }
263
+
264
+ export class MaxIterationsError extends SecurityError {
265
+ override readonly code: string = 'MAX_ITERATIONS_EXCEEDED';
266
+
267
+ constructor(limit: number) {
268
+ super(`Maximum iterations exceeded: ${limit}`);
269
+ }
270
+ }
271
+
272
+ export class MaxRecursionError extends SecurityError {
273
+ override readonly code: string = 'MAX_RECURSION_EXCEEDED';
274
+
275
+ constructor(limit: number) {
276
+ super(`Maximum recursion depth exceeded: ${limit}`);
277
+ }
278
+ }
279
+
280
+ export class MaxExpressionLengthError extends SecurityError {
281
+ override readonly code: string = 'MAX_EXPRESSION_LENGTH_EXCEEDED';
282
+
283
+ constructor(length: number, limit: number) {
284
+ super(`Expression length ${length} exceeds maximum ${limit}`);
285
+ }
286
+ }
287
+
288
+ // Concrete class for general errors
289
+ export class GeneralFormulaError extends FormulaEngineError {
290
+ readonly code: string = 'GENERAL_ERROR';
291
+ readonly category: ErrorCategory = 'EVALUATION';
292
+
293
+ constructor(message: string) {
294
+ super(message);
295
+ }
296
+ }