| Batching behavior | Auto-batched per change cycle via StateManager. Multiple mutations within one synchronous cycle produce one set of changed events. Explicit IExpressionChangeTransactionManager.suspend()/continue() available for manual control. docs |
| Cleanup model | Call expression.dispose() to release all StateManager watchers and RxJS subscriptions. Forgetting to dispose is the most common source of leaks. docs |
| Compiled expressions (AOT) | Optional. The rs-x compiler pre-compiles expression strings into optimised JS at build time, replacing the runtime parser entirely. Catches type errors and invalid identifiers before they reach users. Without the compiler, expressions are parsed at runtime. docs |
| Debugging / tooling | Online playground with live evaluation. IExpressionChangeTrackerManager records and replays change history. expression.changeHook provides per-expression diagnostics. expression change tracker manager for structured audit trails. docs |
| Explicit transaction / atomic update API | Yes: IExpressionChangeTransactionManager.suspend() pauses all expression change emission; continue() or commit() flushes queued changes atomically. docs |
| Expression pre-parsing / caching | Yes — in runtime (tree) mode, each unique expression string is parsed once and the resulting AST is cached. Subsequent bindings of the same expression string reuse the cached AST with zero re-parse cost. docs |
| How async data fits in | First-class. Promises and Observables can appear directly in expression paths — rsx('a + b')(model) works if b is an Observable. The expression resolves the latest emitted value and re-evaluates when it changes. No extra glue code or adapter needed. docs |
| How dependencies are tracked | StateManager wraps values in observer proxies. As the expression evaluates, every property read, index access, and function call is recorded. Any proxy mutation triggers re-evaluation of all expressions that touched that path. docs |
| How you define derived values | Write a string expression — rsx('price * qty - discount')(model). The expression IS the derived value: it evaluates immediately and re-evaluates whenever a dependency changes. docs |
| IDE IntelliSense for expressions | Yes — the RS-X VS Code extension resolves identifiers inside rsx('...') strings against your model type, underlines invalid expressions in the editor, and surfaces type errors without leaving the IDE. docs |
| Lazy evaluation of derived values | Eager — expressions re-evaluate as soon as a dependency changes. However, the initial evaluation after binding is deferred to a microtask, so expression.value is undefined immediately after rsx('...')(model). Subscribe to expression.changed (a ReplaySubject) to receive the value once ready, even if subscribing after the first emission. docs |
| Main reactive building block | IExpression<T> — a live binding produced by rsx('expr')(model). Tracks every property, collection item, Promise, and Observable it reads, and re-evaluates when any dependency changes. docs |
| Model requirements | Plain JS/TS objects and arrays work as-is. No ref(), signal(), or @observable wrapping needed. StateManager instruments values transparently through observer proxies. docs |
| Nested / chain property tracking | Each segment of a path like cart.items[0].qty is tracked individually. Replacing cart.items automatically rebinds [0].qty against the new array — no extra code needed. docs |
| Per-expression control over parse / compile strategy | Yes — each expression can independently choose its evaluation strategy: runtime-parsed (tree), pre-parsed from cache, or AOT-compiled. This means hot-path expressions can be compiled while dynamic or user-defined expressions remain runtime-parsed — all within the same application. docs |
| Runtime expression parsing from strings | Yes — a primary differentiator. rsx('price * qty - discount')(model) is parsed at runtime with no build step, compiler plugin, or code generation required. Useful for spreadsheet-like formulas, rule engines, and user-defined expressions. docs |
| Smallest tracked source unit | Individual path segment — a property, array index, Map key, or collection item. Only expressions reading that exact segment re-evaluate. docs |
| TypeScript support | TypeScript-first. rsx<T>('expr')(model) carries the declared return type. All interfaces, injection tokens, and change payloads are typed. docs |
| UI framework coupling | None. Pure TypeScript with no DOM, rendering, or component model dependencies. Can be used alongside any UI framework or in pure Node.js. docs |
| What this is mainly for | A runtime expression engine that binds string formulas to plain JS/TS models. Framework-agnostic — no DOM or UI coupling. Runs in Node.js, browser, or any UI framework. docs |
| What usually re-runs after a change | Only expressions whose tracked paths changed re-evaluate. Each fires its changed observable once per change cycle. Unaffected expressions are not touched. docs |