| 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 |
| 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 |
| 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 |
| 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 |
| 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 |