Member Junction
    Preparing search index...

    Safe expression evaluator that prevents arbitrary code execution while supporting common boolean expressions and property access patterns.

    Supported operations:

    • Comparison: ==, ===, !=, !==, <, >, <=, >=
    • Logical: &&, ||, !
    • Property access: dot notation (e.g., payload.customer.name), including optional chaining (?.)
    • Array access: bracket notation with a literal index/key (e.g., items[0])
    • Safe methods: .length, .includes(), .startsWith(), .endsWith(), .split(), .slice(), ...
    • Array methods: .some(), .every(), .find(), .filter(), .map(), .reduce() — the callback must be an inline arrow function (items.some(i => i.price > 100), never items.some(fn))
    • Approved globals, in call position only: APPROVED_EXPRESSION_GLOBALS (e.g. Math.abs(output.delta) < 5, Number(payload.count) > 3, Object.keys(payload).length)
    • Type checking: typeof

    Safety is enforced by parsing the expression to an AST and walking it against an ALLOWLIST before it is compiled. Node types, operators, methods and free identifiers are all allowlisted: a free identifier must be a context key, an arrow parameter in scope, or an approved global used as a call. An unlisted construct (computed member access with a non-literal key, .constructor/__proto__ access, any call outside the safe-method and approved-global lists, any other identifier, etc.) is rejected at validation time and never reaches the compiler. A structural allowlist cannot be defeated by string concatenation the way a textual denylist can, and it does not over-reject data that merely mentions a reserved word (e.g. name == 'constructor' is a legal comparison).

    SafeExpressionEvaluator

    const evaluator = new SafeExpressionEvaluator();

    // Simple comparison
    const result1 = evaluator.evaluate(
    "status == 'active'",
    { status: 'active' }
    );

    // Nested property access
    const result2 = evaluator.evaluate(
    "payload.customer.tier == 'premium' && payload.order.total > 1000",
    { payload: { customer: { tier: 'premium' }, order: { total: 1500 } } }
    );

    // Array methods
    const result3 = evaluator.evaluate(
    "items.some(item => item.price > 100)",
    { items: [{ price: 50 }, { price: 150 }] }
    );
    Index

    Constructors

    Methods

    • Evaluates a boolean expression against a context object

      Parameters

      • expression: string

        The boolean expression to evaluate

      • Optionalcontext: Record<string, any> = {}

        The context object containing variables. Its keys are the only free names the expression may use; a key named like an approved global shadows it (see ScreenExpression).

      • OptionalenableDiagnostics: boolean = false

        Whether to include diagnostic information

      Returns ExpressionEvaluationResult

      The evaluation result

    • Evaluates multiple expressions and returns all results

      Parameters

      • expressions: { expression: string; name?: string }[]

        Array of expressions to evaluate

      • context: Record<string, any>

        The context object

      Returns Record<string, ExpressionEvaluationResult>

      Map of results by name or index

    • Parameters

      • expression: string
      • OptionalcontextKeys: Iterable<string, any, any>

      Returns { Error?: string; Undecidable?: boolean; Valid: boolean }

      Use ValidateSyntax.

    • Checks whether an expression could be evaluated, without evaluating it.

      The difference matters because evaluate reports two unrelated problems the same way. Given a condition authored against a runtime envelope, an empty context makes payload.x > 1 fail with payload is not defined — indistinguishable, by result shape, from payload.x > failing with Unexpected token. One is a typo the author should be told about at the door; the other is a perfectly good condition that simply has no data yet. A submit-time check built on evaluate therefore refuses every legitimate condition.

      So this parses and never runs. The expression goes through the same policy screen evaluate applies (the AST allowlist), then the same function body evaluate compiles is parsed in strict mode — never compiled, never invoked. Values are never consulted, which is precisely the property wanted: unknown identifiers, absent properties and undefined chains all PASS, because none of them is a syntax error and whether they resolve is a question about a run that has not happened yet. Pass contextKeys when the evaluation context's keys ARE known, and unknown identifiers are refused here exactly as Evaluate would refuse them.

      Parsing is not evaluation, and the distinction is load-bearing for safety: no code is constructed from the expression here, so nothing in it can execute. The policy screen still runs first, so the constructs evaluate refuses are refused here too — those produce a permanent runtime refusal, and an author is better told now.

      Because nothing is compiled, the answer does not depend on the host allowing dynamic code (a strict CSP without unsafe-eval). Undecidable is kept in the result type for callers that check it, and is no longer returned.

      Parameters

      • expression: string

        the expression to check

      • OptionalcontextKeys: Iterable<string, any, any>

        the names the expression may reference, when known; omitted, free identifiers are left for evaluation time (see ScreenExpression)

      Returns { Error?: string; Undecidable?: boolean; Valid: boolean }

      Valid: true when it parses; Valid: false with Error when it does not

    • Screens an expression against the allowlist WITHOUT compiling or running it. Returns the rejection message, or null when every construct is permitted.

      This is the single policy gate for every place MemberJunction compiles a user- or LLM-authored expression: Evaluate, ValidateSyntax, and the field-rule / integration formula engine (FieldTransformEngine), which passes ['value', 'fields'].

      A context key or arrow parameter named like an approved global (Math, JSON, ...) shadows it and is screened as data, so Math.abs(x) with a Math context key is refused because abs is not a safe instance method. Do not pass approved-global names as context keys.

      Parameters

      • expression: string

        the expression to screen

      • allowedIdentifiers: Iterable<string, any, any> | null

        the names the expression may reference as values — the evaluation context keys. Pass null only when they are not yet known (authoring-time syntax checks); free identifiers are then left for evaluation time to decide, while approved globals used as values, callbacks that are not inline arrows, and every structural rule are still enforced.

      Returns string | null