Features

Batching behaviorAuto-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 modelCall expression.dispose() to release all StateManager watchers and RxJS subscriptions. Forgetting to dispose is the most common source of leaks. docs
Debugging / toolingOnline 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 APIYes: IExpressionChangeTransactionManager.suspend() pauses all expression change emission; continue() or commit() flushes queued changes atomically. docs
How async data fits inFirst-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 trackedStateManager 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 valuesWrite 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 blockIExpression<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 requirementsPlain 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 trackingEach 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 stringsYes — 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 unitIndividual path segment — a property, array index, Map key, or collection item. Only expressions reading that exact segment re-evaluate. docs
TypeScript supportTypeScript-first. rsx<T>('expr')(model) carries the declared return type. All interfaces, injection tokens, and change payloads are typed. docs
UI framework couplingNone. 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 forA 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 changeOnly expressions whose tracked paths changed re-evaluate. Each fires its changed observable once per change cycle. Unaffected expressions are not touched. docs