@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
JavaScript
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;
}
}