@axi-engine/expressions 0.2.1 → 0.2.2

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/dist/index.d.mts CHANGED
@@ -227,6 +227,27 @@ type Expression = ExpressionDefinitions[keyof ExpressionDefinitions];
227
227
  /** The union of all possible expression names (keys). uses in ExpressionHandler */
228
228
  type ExpressionName = keyof ExpressionDefinitions;
229
229
 
230
+ /**
231
+ * A safe utility function that performs a basic mathematical operation on two operands.
232
+ *
233
+ * This function includes built-in type checking. It first ensures
234
+ * that both `left` and `right` operands are numbers before performing the calculation.
235
+ * If the type check fails or if an unsupported operator is provided, it will throw
236
+ * a descriptive error.
237
+ *
238
+ * @param op The mathematical operator to apply ('+', '-', '*', '/').
239
+ * @param left The left-hand operand. It is validated to be a number.
240
+ * @param right The right-hand operand. It is validated to be a number.
241
+ * @returns {number} The numerical result of the calculation.
242
+ * @throws {Error} If either `left` or `right` is not a number.
243
+ * @throws {Error} If the `op` is not a recognized `MathOperationType`.
244
+ *
245
+ * @example
246
+ * const result = resolveMath('+', 10, 5); // returns 15
247
+ * const product = resolveMath('*', 2, 3); // returns 6
248
+ */
249
+ declare function resolveMath(op: MathOperationType, left: unknown, right: unknown): number;
250
+
230
251
  /**
231
252
  * Recursively resolves an Operand into its final scalar value.
232
253
  *
@@ -466,4 +487,4 @@ declare class OrExpressionHandler implements ExpressionHandler<OrExpression> {
466
487
  */
467
488
  declare function createExpressionEvaluator(additionalHandlers?: ExpressionHandler[]): ExpressionEvaluator;
468
489
 
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 };
490
+ 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, resolveMath, resolveOperand, resolveOperandAsScalar };
package/dist/index.d.ts CHANGED
@@ -227,6 +227,27 @@ type Expression = ExpressionDefinitions[keyof ExpressionDefinitions];
227
227
  /** The union of all possible expression names (keys). uses in ExpressionHandler */
228
228
  type ExpressionName = keyof ExpressionDefinitions;
229
229
 
230
+ /**
231
+ * A safe utility function that performs a basic mathematical operation on two operands.
232
+ *
233
+ * This function includes built-in type checking. It first ensures
234
+ * that both `left` and `right` operands are numbers before performing the calculation.
235
+ * If the type check fails or if an unsupported operator is provided, it will throw
236
+ * a descriptive error.
237
+ *
238
+ * @param op The mathematical operator to apply ('+', '-', '*', '/').
239
+ * @param left The left-hand operand. It is validated to be a number.
240
+ * @param right The right-hand operand. It is validated to be a number.
241
+ * @returns {number} The numerical result of the calculation.
242
+ * @throws {Error} If either `left` or `right` is not a number.
243
+ * @throws {Error} If the `op` is not a recognized `MathOperationType`.
244
+ *
245
+ * @example
246
+ * const result = resolveMath('+', 10, 5); // returns 15
247
+ * const product = resolveMath('*', 2, 3); // returns 6
248
+ */
249
+ declare function resolveMath(op: MathOperationType, left: unknown, right: unknown): number;
250
+
230
251
  /**
231
252
  * Recursively resolves an Operand into its final scalar value.
232
253
  *
@@ -466,4 +487,4 @@ declare class OrExpressionHandler implements ExpressionHandler<OrExpression> {
466
487
  */
467
488
  declare function createExpressionEvaluator(additionalHandlers?: ExpressionHandler[]): ExpressionEvaluator;
468
489
 
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 };
490
+ 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, resolveMath, resolveOperand, resolveOperandAsScalar };
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  isOperand: () => isOperand,
35
35
  isReferenceOperand: () => isReferenceOperand,
36
36
  isValueOperand: () => isValueOperand,
37
+ resolveMath: () => resolveMath,
37
38
  resolveOperand: () => resolveOperand,
38
39
  resolveOperandAsScalar: () => resolveOperandAsScalar
39
40
  });
@@ -54,8 +55,29 @@ function isOperand(val) {
54
55
  return isValueOperand(val) || isReferenceOperand(val) || isArithmeticOperand(val);
55
56
  }
56
57
 
57
- // src/resolve-operand.ts
58
+ // src/resolve-math.ts
58
59
  var import_utils2 = require("@axi-engine/utils");
60
+ function resolveMath(op, left, right) {
61
+ (0, import_utils2.throwIf)(
62
+ !(0, import_utils2.isNumber)(left) || !(0, import_utils2.isNumber)(right),
63
+ `Require number operands, but got ${typeof left} and ${typeof right}.`
64
+ );
65
+ switch (op) {
66
+ case "+":
67
+ return Number(left) + Number(right);
68
+ case "-":
69
+ return Number(left) - Number(right);
70
+ case "*":
71
+ return Number(left) * Number(right);
72
+ case "/":
73
+ return Number(left) / Number(right);
74
+ default:
75
+ return (0, import_utils2.throwError)(`Unknown arithmetic operator: ${op}`);
76
+ }
77
+ }
78
+
79
+ // src/resolve-operand.ts
80
+ var import_utils3 = require("@axi-engine/utils");
59
81
  function resolveOperand(op, source) {
60
82
  if (isValueOperand(op)) {
61
83
  return op.value;
@@ -66,37 +88,21 @@ function resolveOperand(op, source) {
66
88
  if (isArithmeticOperand(op)) {
67
89
  const leftVal = resolveOperand(op.arithmetic.left, source);
68
90
  const rightVal = resolveOperand(op.arithmetic.right, source);
69
- (0, import_utils2.throwIf)(
70
- !(0, import_utils2.isNumber)(leftVal) || !(0, import_utils2.isNumber)(rightVal),
71
- `Arithmetic operations require number operands, but got ${typeof leftVal} and ${typeof rightVal}.`
72
- );
73
- switch (op.arithmetic.op) {
74
- case "+":
75
- return Number(leftVal) + Number(rightVal);
76
- case "-":
77
- return Number(leftVal) - Number(rightVal);
78
- case "*":
79
- return Number(leftVal) * Number(rightVal);
80
- case "/":
81
- return Number(leftVal) / Number(rightVal);
82
- default:
83
- (0, import_utils2.throwIf)(true, `Unknown arithmetic operator: ${op.arithmetic.op}`);
84
- }
91
+ return resolveMath(op.arithmetic.op, leftVal, rightVal);
85
92
  }
86
- (0, import_utils2.throwIf)(true, `Unknown operand type: ${JSON.stringify(op)}`);
87
- return void 0;
93
+ return (0, import_utils3.throwError)(`Unknown operand type: ${JSON.stringify(op)}`);
88
94
  }
89
95
  function resolveOperandAsScalar(op, source) {
90
96
  const value = resolveOperand(op, source);
91
- (0, import_utils2.throwIf)(
92
- !(0, import_utils2.isScalar)(value),
97
+ (0, import_utils3.throwIf)(
98
+ !(0, import_utils3.isScalar)(value),
93
99
  `Expected a scalar value (string, number, boolean), but got ${typeof value}.`
94
100
  );
95
101
  return value;
96
102
  }
97
103
 
98
104
  // src/expression-evaluator.ts
99
- var import_utils3 = require("@axi-engine/utils");
105
+ var import_utils4 = require("@axi-engine/utils");
100
106
  var ExpressionEvaluator = class {
101
107
  /** @internal A map of registered expression handlers. */
102
108
  handlers = /* @__PURE__ */ new Map();
@@ -110,7 +116,7 @@ var ExpressionEvaluator = class {
110
116
  * is already registered.
111
117
  */
112
118
  register(handler) {
113
- (0, import_utils3.throwIf)(this.handlers.has(handler.type), `Expression handler for: '${handler.type}' expression already registered`);
119
+ (0, import_utils4.throwIf)(this.handlers.has(handler.type), `Expression handler for: '${handler.type}' expression already registered`);
114
120
  this.handlers.set(handler.type, handler);
115
121
  }
116
122
  /**
@@ -127,9 +133,9 @@ var ExpressionEvaluator = class {
127
133
  * evaluation result.
128
134
  */
129
135
  async resolve(expression, data) {
130
- const key = (0, import_utils3.firstKeyOf)(expression);
136
+ const key = (0, import_utils4.firstKeyOf)(expression);
131
137
  const handler = this.handlers.get(key);
132
- (0, import_utils3.throwIfEmpty)(handler, `Can't find expression handler for: '${key}' expression`);
138
+ (0, import_utils4.throwIfEmpty)(handler, `Can't find expression handler for: '${key}' expression`);
133
139
  const context = {
134
140
  resolve: (expression2) => this.resolve(expression2, data),
135
141
  source: () => data
@@ -151,7 +157,7 @@ var AndExpressionHandler = class {
151
157
  };
152
158
 
153
159
  // src/handlers/chance-expression-handler.ts
154
- var import_utils4 = require("@axi-engine/utils");
160
+ var import_utils5 = require("@axi-engine/utils");
155
161
  var ChanceExpressionHandler = class {
156
162
  type = "chance";
157
163
  /**
@@ -172,16 +178,16 @@ var ChanceExpressionHandler = class {
172
178
  async resolve(exp, context) {
173
179
  const resolvedValue = resolveOperandAsScalar(exp.chance, context.source());
174
180
  let numericValue;
175
- if ((0, import_utils4.isNumber)(resolvedValue)) {
181
+ if ((0, import_utils5.isNumber)(resolvedValue)) {
176
182
  numericValue = resolvedValue;
177
- } else if ((0, import_utils4.isString)(resolvedValue)) {
183
+ } else if ((0, import_utils5.isString)(resolvedValue)) {
178
184
  const parsed = parseFloat(resolvedValue.replace("%", "").trim());
179
- (0, import_utils4.throwIf)(isNaN(parsed), `Chance value as a string must be a valid number, but got '${resolvedValue}'.`);
185
+ (0, import_utils5.throwIf)(isNaN(parsed), `Chance value as a string must be a valid number, but got '${resolvedValue}'.`);
180
186
  numericValue = parsed;
181
187
  } else {
182
- (0, import_utils4.throwIf)(true, `Chance value must be a number or a string, but got a boolean.`);
188
+ (0, import_utils5.throwError)(`Chance value must be a number or a string, but got a boolean.`);
183
189
  }
184
- const randomRoll = (0, import_utils4.randInt)(0, 100);
190
+ const randomRoll = (0, import_utils5.randInt)(0, 100);
185
191
  return randomRoll < numericValue;
186
192
  }
187
193
  };
@@ -218,7 +224,7 @@ var ExistsExpressionHandler = class {
218
224
  };
219
225
 
220
226
  // src/handlers/in-expression-handler.ts
221
- var import_utils5 = require("@axi-engine/utils");
227
+ var import_utils6 = require("@axi-engine/utils");
222
228
  var InExpressionHandler = class {
223
229
  type = "in";
224
230
  /**
@@ -242,13 +248,13 @@ var InExpressionHandler = class {
242
248
  async resolve(exp, context) {
243
249
  const value = resolveOperandAsScalar(exp.in.value, context.source());
244
250
  const rawArray = Array.isArray(exp.in.array) ? exp.in.array : resolveOperand(exp.in.array, context.source());
245
- (0, import_utils5.throwIf)(
251
+ (0, import_utils6.throwIf)(
246
252
  !Array.isArray(rawArray),
247
253
  `The 'in' expression requires an array, but the provided source resolved to ${typeof rawArray}.`
248
254
  );
249
255
  const typedArray = rawArray;
250
256
  const resolvedArray = typedArray.map(
251
- (item) => (0, import_utils5.isScalar)(item) ? item : resolveOperandAsScalar(item, context.source())
257
+ (item) => (0, import_utils6.isScalar)(item) ? item : resolveOperandAsScalar(item, context.source())
252
258
  );
253
259
  return resolvedArray.includes(value);
254
260
  }
@@ -293,9 +299,7 @@ function createExpressionEvaluator(additionalHandlers) {
293
299
  evaluator.register(new LiteralExpressionHandler());
294
300
  evaluator.register(new NotExpressionHandler());
295
301
  evaluator.register(new OrExpressionHandler());
296
- if (additionalHandlers) {
297
- additionalHandlers.forEach((handler) => evaluator.register(handler));
298
- }
302
+ additionalHandlers?.forEach((handler) => evaluator.register(handler));
299
303
  return evaluator;
300
304
  }
301
305
  // Annotate the CommonJS export names for ESM import in node:
@@ -314,6 +318,7 @@ function createExpressionEvaluator(additionalHandlers) {
314
318
  isOperand,
315
319
  isReferenceOperand,
316
320
  isValueOperand,
321
+ resolveMath,
317
322
  resolveOperand,
318
323
  resolveOperandAsScalar
319
324
  });
package/dist/index.mjs CHANGED
@@ -13,8 +13,29 @@ function isOperand(val) {
13
13
  return isValueOperand(val) || isReferenceOperand(val) || isArithmeticOperand(val);
14
14
  }
15
15
 
16
+ // src/resolve-math.ts
17
+ import { isNumber, throwError, throwIf } from "@axi-engine/utils";
18
+ function resolveMath(op, left, right) {
19
+ throwIf(
20
+ !isNumber(left) || !isNumber(right),
21
+ `Require number operands, but got ${typeof left} and ${typeof right}.`
22
+ );
23
+ switch (op) {
24
+ case "+":
25
+ return Number(left) + Number(right);
26
+ case "-":
27
+ return Number(left) - Number(right);
28
+ case "*":
29
+ return Number(left) * Number(right);
30
+ case "/":
31
+ return Number(left) / Number(right);
32
+ default:
33
+ return throwError(`Unknown arithmetic operator: ${op}`);
34
+ }
35
+ }
36
+
16
37
  // src/resolve-operand.ts
17
- import { isNumber, isScalar, throwIf } from "@axi-engine/utils";
38
+ import { isScalar, throwError as throwError2, throwIf as throwIf2 } from "@axi-engine/utils";
18
39
  function resolveOperand(op, source) {
19
40
  if (isValueOperand(op)) {
20
41
  return op.value;
@@ -25,29 +46,13 @@ function resolveOperand(op, source) {
25
46
  if (isArithmeticOperand(op)) {
26
47
  const leftVal = resolveOperand(op.arithmetic.left, source);
27
48
  const rightVal = resolveOperand(op.arithmetic.right, source);
28
- throwIf(
29
- !isNumber(leftVal) || !isNumber(rightVal),
30
- `Arithmetic operations require number operands, but got ${typeof leftVal} and ${typeof rightVal}.`
31
- );
32
- switch (op.arithmetic.op) {
33
- case "+":
34
- return Number(leftVal) + Number(rightVal);
35
- case "-":
36
- return Number(leftVal) - Number(rightVal);
37
- case "*":
38
- return Number(leftVal) * Number(rightVal);
39
- case "/":
40
- return Number(leftVal) / Number(rightVal);
41
- default:
42
- throwIf(true, `Unknown arithmetic operator: ${op.arithmetic.op}`);
43
- }
49
+ return resolveMath(op.arithmetic.op, leftVal, rightVal);
44
50
  }
45
- throwIf(true, `Unknown operand type: ${JSON.stringify(op)}`);
46
- return void 0;
51
+ return throwError2(`Unknown operand type: ${JSON.stringify(op)}`);
47
52
  }
48
53
  function resolveOperandAsScalar(op, source) {
49
54
  const value = resolveOperand(op, source);
50
- throwIf(
55
+ throwIf2(
51
56
  !isScalar(value),
52
57
  `Expected a scalar value (string, number, boolean), but got ${typeof value}.`
53
58
  );
@@ -56,7 +61,7 @@ function resolveOperandAsScalar(op, source) {
56
61
 
57
62
  // src/expression-evaluator.ts
58
63
  import {
59
- throwIf as throwIf2,
64
+ throwIf as throwIf3,
60
65
  throwIfEmpty,
61
66
  firstKeyOf
62
67
  } from "@axi-engine/utils";
@@ -73,7 +78,7 @@ var ExpressionEvaluator = class {
73
78
  * is already registered.
74
79
  */
75
80
  register(handler) {
76
- throwIf2(this.handlers.has(handler.type), `Expression handler for: '${handler.type}' expression already registered`);
81
+ throwIf3(this.handlers.has(handler.type), `Expression handler for: '${handler.type}' expression already registered`);
77
82
  this.handlers.set(handler.type, handler);
78
83
  }
79
84
  /**
@@ -114,7 +119,7 @@ var AndExpressionHandler = class {
114
119
  };
115
120
 
116
121
  // src/handlers/chance-expression-handler.ts
117
- import { isNumber as isNumber2, isString, randInt, throwIf as throwIf3 } from "@axi-engine/utils";
122
+ import { isNumber as isNumber2, isString, randInt, throwError as throwError3, throwIf as throwIf4 } from "@axi-engine/utils";
118
123
  var ChanceExpressionHandler = class {
119
124
  type = "chance";
120
125
  /**
@@ -139,10 +144,10 @@ var ChanceExpressionHandler = class {
139
144
  numericValue = resolvedValue;
140
145
  } else if (isString(resolvedValue)) {
141
146
  const parsed = parseFloat(resolvedValue.replace("%", "").trim());
142
- throwIf3(isNaN(parsed), `Chance value as a string must be a valid number, but got '${resolvedValue}'.`);
147
+ throwIf4(isNaN(parsed), `Chance value as a string must be a valid number, but got '${resolvedValue}'.`);
143
148
  numericValue = parsed;
144
149
  } else {
145
- throwIf3(true, `Chance value must be a number or a string, but got a boolean.`);
150
+ throwError3(`Chance value must be a number or a string, but got a boolean.`);
146
151
  }
147
152
  const randomRoll = randInt(0, 100);
148
153
  return randomRoll < numericValue;
@@ -181,7 +186,7 @@ var ExistsExpressionHandler = class {
181
186
  };
182
187
 
183
188
  // src/handlers/in-expression-handler.ts
184
- import { isScalar as isScalar2, throwIf as throwIf4 } from "@axi-engine/utils";
189
+ import { isScalar as isScalar2, throwIf as throwIf5 } from "@axi-engine/utils";
185
190
  var InExpressionHandler = class {
186
191
  type = "in";
187
192
  /**
@@ -205,7 +210,7 @@ var InExpressionHandler = class {
205
210
  async resolve(exp, context) {
206
211
  const value = resolveOperandAsScalar(exp.in.value, context.source());
207
212
  const rawArray = Array.isArray(exp.in.array) ? exp.in.array : resolveOperand(exp.in.array, context.source());
208
- throwIf4(
213
+ throwIf5(
209
214
  !Array.isArray(rawArray),
210
215
  `The 'in' expression requires an array, but the provided source resolved to ${typeof rawArray}.`
211
216
  );
@@ -256,9 +261,7 @@ function createExpressionEvaluator(additionalHandlers) {
256
261
  evaluator.register(new LiteralExpressionHandler());
257
262
  evaluator.register(new NotExpressionHandler());
258
263
  evaluator.register(new OrExpressionHandler());
259
- if (additionalHandlers) {
260
- additionalHandlers.forEach((handler) => evaluator.register(handler));
261
- }
264
+ additionalHandlers?.forEach((handler) => evaluator.register(handler));
262
265
  return evaluator;
263
266
  }
264
267
  export {
@@ -276,6 +279,7 @@ export {
276
279
  isOperand,
277
280
  isReferenceOperand,
278
281
  isValueOperand,
282
+ resolveMath,
279
283
  resolveOperand,
280
284
  resolveOperandAsScalar
281
285
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axi-engine/expressions",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,18 +24,18 @@
24
24
  }
25
25
  },
26
26
  "scripts": {
27
- "test": "vitest run --root ../../ src/",
28
27
  "build": "tsup",
29
28
  "prebuild": "npm test",
29
+ "test": "vitest run --root ../../ src/",
30
30
  "docs": "typedoc src/index.ts --out docs/api --options ../../typedoc.json"
31
31
  },
32
32
  "files": [
33
33
  "dist"
34
34
  ],
35
35
  "devDependencies": {
36
- "@axi-engine/utils": "^0.2.3"
36
+ "@axi-engine/utils": "^0.2.5"
37
37
  },
38
38
  "peerDependencies": {
39
- "@axi-engine/utils": "^0.2.3"
39
+ "@axi-engine/utils": "^0.2.5"
40
40
  }
41
41
  }