tina4-nodejs 3.10.30 → 3.10.31

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.10.30",
3
+ "version": "3.10.31",
4
4
  "type": "module",
5
5
  "description": "This is not a framework. Tina4 for Node.js/TypeScript — zero deps, 38 built-in features.",
6
6
  "keywords": ["tina4", "framework", "web", "api", "orm", "graphql", "websocket", "typescript"],
@@ -419,6 +419,40 @@ function evalExpr(expr: string, context: Record<string, unknown>): unknown {
419
419
  }
420
420
  }
421
421
 
422
+ // Arithmetic operators: +, -, *, //, /, %, ** (lowest to highest precedence)
423
+ for (const op of [" + ", " - ", " * ", " // ", " / ", " % ", " ** "]) {
424
+ const pos = findOutsideQuotes(expr, op);
425
+ if (pos >= 0) {
426
+ const left = expr.slice(0, pos).trim();
427
+ const right = expr.slice(pos + op.length).trim();
428
+ const lVal = evalExpr(left, context);
429
+ const rVal = evalExpr(right, context);
430
+ try {
431
+ let lNum = lVal != null ? Number(lVal) : 0;
432
+ let rNum = rVal != null ? Number(rVal) : 0;
433
+ if (isNaN(lNum)) lNum = 0;
434
+ if (isNaN(rNum)) rNum = 0;
435
+ const opS = op.trim();
436
+ // Preserve int type when both operands are int-like (except for / which returns float)
437
+ const bothInt = Number.isInteger(lNum) && Number.isInteger(rNum) && opS !== "/";
438
+ let result: number;
439
+ switch (opS) {
440
+ case "+": result = lNum + rNum; break;
441
+ case "-": result = lNum - rNum; break;
442
+ case "*": result = lNum * rNum; break;
443
+ case "//": result = rNum !== 0 ? Math.floor(lNum / rNum) : 0; break;
444
+ case "/": result = rNum !== 0 ? lNum / rNum : 0; break;
445
+ case "%": result = rNum !== 0 ? lNum % rNum : 0; break;
446
+ case "**": result = lNum ** rNum; break;
447
+ default: result = 0;
448
+ }
449
+ return bothInt && Number.isInteger(result) ? result : result;
450
+ } catch {
451
+ return null;
452
+ }
453
+ }
454
+ }
455
+
422
456
  // Function call: name("arg1", "arg2") — supports dotted names like user.t("key")
423
457
  const fnMatch = expr.match(FN_CALL_RE);
424
458
  if (fnMatch) {
@@ -1782,7 +1816,7 @@ export class Frond {
1782
1816
  if (m) {
1783
1817
  const name = m[1];
1784
1818
  const expr = m[2].trim();
1785
- context[name] = evalExpr(expr, context);
1819
+ context[name] = this.evalVarRaw(expr, context);
1786
1820
  }
1787
1821
  }
1788
1822