UNPKG

@statezero/core

Version:

The type-safe frontend client for StateZero - connect directly to your backend models with zero boilerplate

92 lines (91 loc) 3.29 kB
import { ValidationError } from './errors'; import * as math from 'mathjs'; // Create a math instance const mathInstance = math.create(); // Define the allowed functions that we want to keep const allowedFunctions = ['abs', 'round', 'floor', 'ceil', 'min', 'max']; // Define allowed operators const ALLOWED_OPERATORS = ['+', '-', '*', '/', '%', '^']; export function F(expression) { if (typeof expression !== 'string' || !expression) { throw new ValidationError('F expression requires a non-empty string'); } try { const node = math.parse(expression); validateExpression(node); return { __f_expr: true, original_expr: expression, ast: node.toJSON() }; } catch (err) { throw new ValidationError(`Invalid F expression: ${err.message}`); } } function validateExpression(node) { if (!node) return; if (node.type === 'OperatorNode') { if (!ALLOWED_OPERATORS.includes(node.op)) { throw new ValidationError(`Unsupported operator: ${node.op}`); } node.args.forEach(validateExpression); } else if (node.type === 'FunctionNode') { if (!allowedFunctions.includes(node.name)) { throw new ValidationError(`Function not allowed: ${node.name}`); } node.args.forEach(validateExpression); } else if (node.type === 'SymbolNode') { if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(node.name)) { throw new ValidationError(`Invalid field name: ${node.name}`); } } else if (node.type === 'ConstantNode') { // Constants are fine } else if (node.type === 'ParenthesisNode') { validateExpression(node.content); } else if (node.type === 'ConditionalNode') { throw new ValidationError('Conditional expressions are not supported in F expressions'); } else if (node.type === 'AssignmentNode') { throw new ValidationError('Assignment expressions are not supported in F expressions'); } else if (node.type === 'BlockNode') { throw new ValidationError('Block expressions are not supported in F expressions'); } else if (node.type === 'AccessorNode') { throw new ValidationError('Accessor expressions are not supported in F expressions'); } else if (node.type === 'IndexNode') { throw new ValidationError('Index expressions are not supported in F expressions'); } else if (node.type === 'RangeNode') { throw new ValidationError('Range expressions are not supported in F expressions'); } else if (node.type === 'ArrayNode') { throw new ValidationError('Array expressions are not supported in F expressions'); } else if (node.type === 'ObjectNode') { throw new ValidationError('Object expressions are not supported in F expressions'); } else { throw new ValidationError(`Unsupported node type: ${node.type}`); } } export function evaluateExpression(expr, data) { if (!expr || !expr.__f_expr) { return expr; } try { return math.evaluate(expr.original_expr, data); } catch (err) { console.warn(`Error evaluating F expression: ${err.message}`); return null; } }