State management that never touches the UI
Define your business logic in standard JavaScript classes. Let Strator wrap your state in reactive Proxies when mounted to the UI to automatically dispatch updates—delivering lightning-fast, zero-mock testability across any framework.
Test pure classes in <1ms
Strict Model / View separation
Zero unnecessary re-renders
React, Vue, Svelte, Vanilla, Node
import { Model } from "@strator/core";
export interface CartState {
items: Array<{ id: string; name: string; price: number }>;
discount: number;
}
export class CartModel extends Model<CartState> {
static initialState: CartState = { items: [], discount: 0 };
addItem(item: { id: string; name: string; price: number }) {
// State is proxied when mounted to UI, automatically dispatching updates
this.state.items.push(item);
}
getTotal(): number {
const raw = this.state.items.reduce((sum, item) => sum + item.price, 0);
return raw * (1 - this.state.discount);
}
}import { useLocalModel } from "@strator/react";
import { CartModel } from "./CartModel";
export function CartView() {
// Hook seamlessly provides reactive Dispatcher and binds fine-grained state
const [cart, items] = useLocalModel(CartModel, (s) => s.items);
return (
<div className="cart-box">
<h3>Items: {items.length}</h3>
<button
onClick={() =>
cart.addItem({
id: "1",
name: "Pro Plan",
price: 49,
})
}
>
Add Item (Total: ${cart.getTotal()})
</button>
</div>
);
}import { describe, it, expect } from "vitest";
import { CartModel } from "./CartModel";
describe("CartModel business logic", () => {
it("calculates total with discount cleanly without ANY UI rendering or mocks", () => {
// Simply instantiate standard JavaScript class!
const cart = new CartModel({ items: [], discount: 0.1 });
cart.addItem({ id: "sku_1", name: "Toolkit", price: 100 });
// 100% deterministic, instant execution (<1ms)
expect(cart.state.items).toHaveLength(1);
expect(cart.getTotal()).toBe(90);
});
});How Strator separates the View from the Model
A battle-tested architecture that isolates state transitions inside pure ES classes while seamlessly providing reactive bindings to UI components.
Standard JavaScript Class
Holds state, domain methods, business validation, and API integrations. Zero React/DOM dependencies.
class CounterModel extends Model {
// Pure properties
state = { count: 0 };
increase() { this.state.count++; }
}Proxy Reactivity & Dispatcher
Wraps state in an ES Proxy and supplies the reactive Dispatcher only when wired to UI components.
Declarative UI Component
Consumes models via clean hooks (useLocalModel, useGlobalModel). Renders state and triggers actions.
function View() {
const [m, count] = useLocalModel(
CounterModel, (s) => s.count
);
return <button onClick={() => m.increase()}>
Count: {count}
</button>;
}Interactive Lifecycle Simulator
Step through what happens inside Strator during a single state mutation.
See the Reactive Dispatcher in Real Time
Interact with the live UI on the left and watch the Strator Dispatcher and Proxy mutation stream in the live telemetry console on the right.
Unit test business logic with Zero UI Mocks
Because Strator models are pure JavaScript classes, you never need JSDOM, React test harnesses, mock dispatchers, or brittle UI click simulations to verify critical business logic.
// Requires JSDOM, React wrappers, and heavy providers
import { render, screen, fireEvent } from "@testing-library/react";
import { Provider } from "react-redux";
import { store } from "../store";
import { CheckoutModal } from "../components/CheckoutModal";
test("applies discount coupon", async () => {
// 1. Heavy DOM mount
render(
<Provider store={store}>
<CheckoutModal />
</Provider>
);
// 2. Brittle selector queries & async DOM events
const input = screen.getByPlaceholderText(/coupon/i);
fireEvent.change(input, { target: { value: "SAVE20" } });
fireEvent.click(screen.getByRole("button", { name: /apply/i }));
// 3. Slow assertions on rendered text
expect(await screen.findByText(/\$80.00/)).toBeInTheDocument();
});// Pure JavaScript. No DOM. No React harness. No Mocks.
import { describe, it, expect } from "vitest";
import { CheckoutModel } from "../models/CheckoutModel";
it("applies discount coupon", () => {
// 1. Direct class instantiation with state
const model = new CheckoutModel({ cartTotal: 100, discount: 0 });
// 2. Direct method invocation
model.applyCoupon("SAVE20");
// 3. Pure deterministic state & computed assertions
expect(model.state.discount).toBe(0.2);
expect(model.getFinalPrice()).toBe(80);
});Execute 100 Strator Model Unit Tests
Experience instant feedback loops with zero mock instantiation overhead.
Built for developers who value clean architecture
Every feature in Strator is designed to maximize testability, eliminate UI coupling, and minimize cognitive friction.
Pure JavaScript Classes
Define domain logic using standard ES classes, TypeScript types, getters, and methods without framework boilerplate or vendor lock-in.
Automatic Proxied Dispatch
No action decorators, types, or reducers. When mounted to the UI, mutating the reactive proxied state automatically dispatches granular updates.
Deep Proxy Reactivity
Mutate arrays and nested objects naturally. Strator's reactive Proxy traps mutations down to the exact property path.
Zero-Mock Unit Testing
Instantiate models directly in Vitest or Jest. Verify complex domain logic without mounting React components or simulating clicks.
UI-Framework Agnostic
Run the same Model across React, Vue, Svelte, or even Node.js CLI tools. Your business layer becomes truly portable.
Flexible State Scopes
Choose between useLocalModel (component-scoped), useSharedModel (keyed sharing), and useGlobalModel (app-wide singleton).
Clean code across the full stack
Look at how effortless it is to define a Model, wire it to React, test it in Vitest, and reuse it anywhere.
import { Model } from "@strator/core";
export interface UserState {
profile: { name: string; email: string } | null;
isLoading: boolean;
error: string | null;
}
export class UserModel extends Model<UserState> {
static initialState: UserState = {
profile: null,
isLoading: false,
error: null,
};
public async fetchUserProfile(userId: string) {
this.state.isLoading = true;
this.state.error = null;
try {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error("Failed to load user profile");
this.state.profile = await res.json();
} catch (err: any) {
this.state.error = err.message;
} finally {
this.state.isLoading = false;
}
}
public updateEmail(newEmail: string) {
if (!this.state.profile) throw new Error("No profile loaded");
this.state.profile.email = newEmail;
}
}How Strator compares
A direct feature comparison for engineering teams seeking maximum testability and minimal boilerplate.
| Feature / Capability | Strator | Redux Toolkit | MobX | Zustand |
|---|---|---|---|---|
| True MVVM Separation (Logic isolated from View) | Yes Core architectural foundation | No Coupled via action types/dispatch | Yes Observable classes | No Hook-bound closures |
| Zero-Mock Unit Testing (Pure Class Instantiation) | Yes new Model(state) & test directly | No Requires store config & mocks | Yes Direct class testing | No Requires hook wrappers / reset hacks |
| Automatic Proxied Dispatch | Yes Proxy state automatically dispatches | No Manual createAsyncThunk / actions | Yes @action decorator | No Manual function assignments |
| Multi-Framework Portability (React, Vue, Svelte, Node) | Yes 100% Agnostic core | Yes Agnostic core, but verbose | Yes Multi-framework bindings | No Primarily React focused |
| Deep Nested Proxy Path Reactivity | Yes Tracks exact nested property path | No Immer produces new tree refs | Yes Observable getters | No Manual selectors required |
| Boilerplate & Mental Overhead | Yes Minimal: Standard JS Class | No High: Slices, Thunks, Selectors | No Moderate: makeObservable rules | Yes Minimal |
| Core Library Footprint | Yes < 2.5 kB min+gzip | No > 12 kB (RTK + Immer + Reselect) | No > 15 kB | Yes ~1.5 kB |
Get up and running in minutes
Follow these 3 simple steps to add MVVM state management and zero-mock testing to your app.
Install packages
Define your domain model
Extend Model<T> and write standard class methods. State is proxied when mounted to the UI to dispatch updates.
import { Model } from "@strator/core";
export interface CounterState {
count: number;
}
export class CounterModel extends Model<CounterState> {
static initialState: CounterState = { count: 0 };
public increase() {
this.state.count += 1;
}
public reset() {
this.state.count = 0;
}
}Connect to your UI component
Use useLocalModel or useGlobalModel with a selector for fine-grained re-renders.
import React from "react";
import { useLocalModel } from "@strator/react";
import { CounterModel } from "./CounterModel";
export function Counter() {
const [model, count] = useLocalModel(CounterModel, (state) => state.count);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => model.increase()}>Increment</button>
<button onClick={() => model.reset()}>Reset</button>
</div>
);
}React Bindings Documentation →
Learn about selector snapshot diffing, local & shared model hooks, SSR hydration, and memory best practices.
Vue Bindings Documentation →
Learn about createStrator plugin, native reactive proxies, syncState recursive syncing, and Composition API usage.
Everything you need to know
Answers to common questions about Strator's architecture, Proxy reactivity, and testing methodology.