Install dependencies, load RsXExpressionParserModule once during startup, then bind with rsx('...')(model) and subscribe to changed.
What is rs-x?
rs-x is a reactive expression library for TypeScript. You write a plain string expression — such as 'price * quantity - discount' — bind it to a model object, and get back an IExpression<T> that always holds the current computed value. Whenever any tracked dependency on the model changes, the expression re-evaluates and fires a changed event.
Dependencies are tracked automatically — you do not declare them up front. rs-x wraps values in observer proxies and detects mutations as they happen, whether those mutations come from a direct property assignment, an array push, a resolved Promise, or an RxJS Observable emission.
Expressions are composable: an expression value can itself be a model field, a collection item, a function return value, or another expression. The whole dependency graph updates incrementally — only the nodes affected by a mutation are re-evaluated.
Quick flow
Install: add @rs-x/core, @rs-x/state-manager, @rs-x/expression-parser, and rxjs.
Bootstrap once: load RsXExpressionParserModule into InjectionContainer at app startup.
Bind + observe: create with rsx(...)(model), read value, subscribe to changed.
Clean up: call expression.dispose() when the expression is no longer needed.
What each package provides
rs-x is split into three focused packages so you can take only what you need:
@rs-x/core — foundational contracts, the Inversify-based InjectionContainer, utility types, and helpers such as WaitForEvent and printValue. Every other rs-x package depends on core.
@rs-x/state-manager — tracks mutations on plain objects, arrays, Maps, Sets, Dates, Promises, and Observables by wrapping values in transparent observer proxies. The StateManager service stores the current value for each watched (context, index) pair and emits IStateChange events. Used internally by the expression parser.
@rs-x/expression-parser — parses expression strings into an AST, resolves identifiers against the model via StateManager, and produces live IExpression<T> instances. The entrypoint is rsx(); the module is RsXExpressionParserModule.
rxjs — peer dependency. Observable values in expressions are resolved using RxJS Observable. The changed stream on every expression is also an RxJS Observable.
Create — rsx<T>('expr')(model) parses the expression string, resolves each identifier against the model, registers watchers with StateManager, and returns an IExpression<T>. The value property is populated immediately — no need to wait for a change event.
Observe — subscribe to expression.changed (an RxJS Observable) to react to re-evaluations. The callback fires synchronously for plain property mutations and asynchronously for Promises and Observables. Always read the latest value from expression.value inside the handler — the change event itself does not carry the new value.
Clean up — call expression.dispose() when the expression is no longer needed. This releases all StateManager watchers and RxJS subscriptions. Forgetting to dispose is the most common source of memory leaks.
// 1. Create — parse the expression and bind it to the model.// expression.value is available immediately.constexpression=rsx<number>('a * b')(model);// 2. Observe — subscribe to the changed stream.// Fires synchronously for plain-property mutations,// asynchronously for Promises and Observables.constsubscription=expression.changed.subscribe(()=>{console.log(expression.value);});// 3. Clean up — call dispose() to release all watchers.// Always dispose expressions you no longer need to// prevent memory leaks.expression.dispose();
Next steps
Once the basics are working, explore the full feature set:
Expression types — full list of supported syntax: arithmetic, comparisons, ternary, member access, function calls, and more.
Member expressions — nested property access (cart.items[0].qty) and how rs-x tracks each step in the chain.
Async operations — how Promises and Observables participate in the reactive graph and what the resolved value looks like before and after settlement.
Collections — reactive arrays, Maps, and Sets — mutation methods like push, set, and delete all trigger updates.
Read-only properties — expose derived or computed values as observable model fields using StateManager.setState.
Dependency injection — how rs-x uses Inversify under the hood and how to load custom modules alongside the default ones.
Observation strategy — a detailed look at how each built-in value type is observed and when the proxy wrapping happens.