Skip to main content
INDEPENDENT RESEARCH · COMPILER & RUNTIME · REVENUE CLOUD (RCA)Tier 1 Flagship

Local Evaluation of Salesforce RCA - CML

“When every configurator click pays a server round-trip, move evaluation to the client - but do it with a real language runtime, not duplicated if-statements.”

An independent compiler experiment: parsing a declarative constraint language into a typed AST and running it in the browser - in LWC and in React - so configurators stop paying a server round-trip per click.

CONSTRAINT COMPILERTYPESCRIPT ASTHEADLESS COMMERCEREACT & LWCCLIENT EVALUATION

Executive BriefCompiler
Domain / ScopeCompiler & Runtime Engineering
Architectural RoleIndependent Architect & Builder
Timeline & InitiativeIndependent research · 2025–2026
Key Verified Invariant

Interactive evaluation moved from per-click server latency to in-memory execution (low single-digit ms across my differential harness models)

01 · The Constraint

What Made the Problem Difficult

This is independent work - no employer code, no client config, no proprietary schemas. I kept seeing the same shape in enterprise quoting: a product model with hundreds of interdependent rules (eligibility, co-requisites, pricing attributes), one engine on the server, and two or three hand-maintained copies of the logic leaking into storefront JavaScript. I wanted to know whether a single, honest compiler core could serve both a CRM-embedded UI and a headless React surface without drift. So I built it as a weekend project, against a rule grammar I defined myself, informed only by public documentation patterns.

Core Platform Constraint

Systemic Friction

The failure mode I kept observing in Salesforce Revenue Cloud (RCA) configurators:

01
Per-click round-trips

Every toggle or attribute edit goes remote. At 300–800ms a click, fast configurators feel broken, and overlapping responses overwrite newer state.

02
Headless gets second-class validation

The storefront either polls the server per keystroke (slow, rate-limit hungry) or re-implements rules by hand.

03
Hand ports drift

Two implementations of the same pricing logic always diverge. The bug reports arrive months later, as misquotes.

04
Sandbox says no to shortcuts

In LWC, eval() and new Function() are off the table under Lightning Web Security, so 'just compile to JS strings' is not an option.

Boundary Invariants & Operational Limits:
  • !No server calls during interaction: once rules are loaded, option and attribute changes evaluate locally
  • !One rule implementation: the LWC path and the React path execute the same AST, not two ports
  • !Sandbox-safe: no eval(), no new Function(), strict CSP-compatible
  • !Fast enough to disappear: multi-rule evaluation in low single-digit milliseconds on commodity hardware
  • !Small and shippable: zero-dependency bundle (~30KB class) via static resources and NPM
02 · The Architecture

What System Was Designed

Four stages, all in plain TypeScript, no dependencies: 1. Scanner & parser: a hand-written lexer and recursive-descent parser turns rule text into a typed AST - precedence, negation, nested booleans, function calls. Nothing clever, just careful. 2. Scope check & dependency graph: variable bindings resolve against a catalog schema I define per demo model, producing a DAG so cascading attributes (A changes B changes C) evaluate in topological order with cycle detection. 3. Tree-walking evaluator: a small interpreter with deterministic coercion and memoised subtrees. Boring on purpose - the same input always produces the same output, on either runtime. 4. Two packages, one core: ESM for React/Next.js, IIFE/UMD for LWC via platformResourceLoader. A differential harness runs thousands of generated cases through both and diffs the results.

03 · The Key Decision

Pivotal Architectural Choices

Key Architectural Decisions

ADR // 01DETERMINISTIC CHOICE

Client-side evaluation core vs. per-click server calls

Context:

The default I saw everywhere was remote evaluation per interaction - correct, but slow and racy.

Alternatives Evaluated:
  • Server-side evaluators per keystroke
  • Remote evaluation REST API
  • Regex-based client parsing
Chosen: A real compiler core in dependency-free TypeScript, evaluated in memory.

The network was the budget, not the CPU. Moving evaluation client-side removed the round-trip entirely; keeping it a proper AST (not string hacks) kept semantics identical across runtimes.

ADR // 02DETERMINISTIC CHOICE

Tree-walking interpreter vs. generating JS functions

Context:

new Function() would be faster on paper, but LWS and enterprise CSP forbid it - and rightly so.

Alternatives Evaluated:
  • Dynamic eval() code generation
  • Server-compiled bytecode cache
Chosen: AST interpreter with deterministic coercion and node memoisation.

Compliance first, then speed. Short-circuit booleans and memoised subtrees kept my test models in the low single-digit milliseconds, which is fast enough that nobody notices the interpreter.

ADR // 03DETERMINISTIC CHOICE

One shared library vs. Apex-plus-JavaScript dual maintenance

Context:

Every team I watched tried the dual implementation at least once. Every one of them paid for it in misquotes.

Alternatives Evaluated:
  • Hand-maintained storefront re-implementation
  • Iframing the CRM UI
  • Proxying storefront clicks through a gateway
Chosen: A single canonical TS library consumed by both LWC and React.

One grammar, one parser, one evaluator. Drift becomes structurally impossible rather than process-prevented.

ADR // 04DETERMINISTIC CHOICE

Topological DAG resolution vs. multi-pass dirty-checking

Context:

Cascading attributes loop forever under naive re-evaluation.

Alternatives Evaluated:
  • Bounded multi-pass loops
  • Chained event listeners
Chosen: DAG with topological order and cycle detection at load time.

Single pass, deterministic order, and circular rules fail loudly at authoring time instead of freezing a UI thread.

04 · The Engineering

What Was Actually Built

01

Hand-wrote the scanner and parser - no generator dependencies - covering arithmetic, comparisons, nested AND/OR/NOT, and domain functions, with error messages that point at the offending token.

02

Built the dependency graph as an adjacency list over attribute references; Kahn's algorithm gives the evaluation order and rejects cycles before anything renders.

03

Kept the evaluator allocation-light: pre-sized scopes, memoised pure subtrees, and no code generation of any kind, so it loads cleanly under LWS.

04

Shipped two artifacts from one source: ESM for bundlers and a minified IIFE for LWC static resources; the LWC wrapper is a thin loader, not a fork.

05

Wrote a differential harness that generates thousands of rule/input combinations and asserts byte-identical outcomes between the LWC bundle and the React import - that harness is the real parity guarantee.

06

Added a build-time rule linter that flags unknown attributes, circular references, and dead constraints before deployment, because authoring errors are cheaper than runtime ones.

05 · The Trade-offs

Deliberate Architectural Compromises

Trade-offs & Mitigations

⚖Bundle weight vs. network chatter

Architectural Benefit:

Interaction evaluation happens in memory with no network at all.

Associated Cost:

The client downloads and parses a small compiler library up front.

Mitigation Strategy:

Kept it dependency-free and async-loadable; on my test models it parses in well under a frame.

⚖Bounded grammar vs. full macro language

Architectural Benefit:

Evaluation time stays predictable - no runaway recursion in the UI thread.

Associated Cost:

No unbounded recursive macros in the rule language.

Mitigation Strategy:

Everything I actually needed in configurators fits declarative constraints; macros were never worth the risk.

⚖Interpreter overhead vs. JIT-compiled functions

Architectural Benefit:

Fully sandbox- and CSP-safe with no dynamic code.

Associated Cost:

Tree-walking is slower than native JIT per node.

Mitigation Strategy:

Memoisation plus short-circuiting keeps realistic models in the low milliseconds - the network saving dwarfs it.

06 · The Result

Verified Outcomes

✓ VERIFIED RESULT

Interactive evaluation moved from per-click server latency to in-memory execution (low single-digit ms across my differential harness models)

✓ VERIFIED RESULT

One rule implementation serves both LWC and React paths - verified by differential tests, not by convention

✓ VERIFIED RESULT

Authoring errors surface at build time (unknown attributes, cycles, dead rules) instead of as production misquotes

✓ VERIFIED RESULT

Sandbox-safe by construction: zero dynamic code generation, loads under LWS and strict CSP

✓ VERIFIED RESULT

Reusable across demo models without modification - the engine knows nothing about any employer's catalog

07 · Architecture Blueprint

System Schematic & Data Flow

System Topology Blueprint
Constraint Compiler & Deterministic Evaluation
INTERACTIVE SCHEMATIC
STAGE 01 · INPUTCML Rules (RCA)Constraint Modeling LangSalesforce Revenue CloudparseSTAGE 02 · TS COMPILER RUNTIME1. Lexer & AST Generation2. Semantic Rule Validation3. Deterministic Evaluation CoreESM / LWSNPM / ReusableRUNTIME TARGET 01Salesforce Native LWCLightning Web SecurityStatic Resource bundleRUNTIME TARGET 02Modern React ClientStandalone / HeadlessZero-dependency TS library
ZERO ROUND-TRIPEvaluates complex CML rule trees synchronously in client memory.
DETERMINISTIC ASTIdentical evaluation semantics guaranteed across LWC and React runtimes.
LWS COMPLIANTEngineered without eval() to strictly satisfy Lightning Web Security.

Declarative rules compile to a typed AST once, then evaluate in memory on either runtime - no per-click server calls.

Text alternative for screen readers: Architecture flow: Rule Source (demo grammar) to TS Lexer & Parser via Recursive-Descent AST; TS Lexer & Parser to Scope Check & DAG via Bindings & Order; Scope Check & DAG to Deterministic Evaluator via In-Memory, Single-Digit ms; Deterministic Evaluator to LWC & React Targets via One Core, Two Bundles

08 · Why It Matters

Architectural Conclusion

“Most configurator slowness is not a UI problem - it is an evaluation-placement problem. Put a real language runtime where the clicks happen and the whole architecture simplifies.”

Key Lessons for Platform Scale:
  • •When the platform boundary is the bottleneck, owning a small compiler beats adding another cache layer.
  • •A differential test harness across runtimes is worth more than any parity claim in prose.
  • •Interpreters earn their keep in sandboxed environments: predictable, auditable, and fast enough.