Mark root state. Keep the rest JavaScript.
This walkthrough starts with Starbeam’s smallest useful idea: mark the storage that changes, then build the rest of your model with ordinary JavaScript.
You will write a tiny cart model. The public API will look like normal domain
code: cart.items, cart.itemCount, and cart.totalCents. Starbeam makes it
reactive because those reads touch marked root state internally.
Install Starbeam
Section titled “Install Starbeam”For this walkthrough, install the framework-neutral package and the reactive collections package:
pnpm add @starbeam/universal @starbeam/collectionsFramework apps usually install these packages plus the adapter for their
framework, such as @starbeam/react, @starbeam/preact, @starbeam/ember,
@starbeam/vue, or @starbeam/svelte. This page stays framework-neutral;
framework guides cover the adapter APIs.
Choosing packages for a framework app or reusable library? See Install Starbeam.
Import the first root state helper
Section titled “Import the first root state helper”The first example uses a reactive Map for root state:
import { reactive } from "@starbeam/collections";Mark root state with a collection
Section titled “Mark root state with a collection”Starbeam lets you define reactive versions of ordinary JavaScript objects and
built-in collections. They keep their JavaScript and TypeScript surface:
reactive.Map<K, V>() gives you a Map<K, V>, and you use it with the built-in
Map API.
Keep root state private and expose the shape your app wants.
import { reactive } from "@starbeam/collections";
interface LineItem { readonly id: string; readonly name: string; readonly priceCents: number; readonly quantity: number;}
class Cart { #items = reactive.Map<string, LineItem>();
get items(): readonly LineItem[] { return [...this.#items.values()]; }
add(item: LineItem): void { this.#items.set(item.id, item); }
get itemCount(): number { return this.items.reduce((total, item) => total + item.quantity, 0); }
get totalCents(): number { return this.items.reduce( (sum, item) => sum + item.priceCents * item.quantity, 0, ); }}#items is the root state. It is reactive, but it is still a map from string
IDs to LineItem values: use set(), values(), get(), has(), and
delete().
The rest of the class is ordinary JavaScript. items, add(), itemCount, and
totalCents are domain-shaped methods and getters built above the root state.
Collections are the right starting point here because a cart is naturally
collection-shaped. If state is object-shaped, use reactive.object(...) instead.
Derive values with ordinary JavaScript
Section titled “Derive values with ordinary JavaScript”Derived values do not need a wrapper just because they read reactive state. In
the cart above, itemCount and totalCents are plain getters.
const cart = new Cart();
cart.totalCents; // 0
cart.add({ id: "tea", name: "Tea", priceCents: 500, quantity: 2 });
cart.itemCount; // 2cart.totalCents; // 1000The getters read cart.items. cart.items reads the private collection.
Starbeam sees those reads without asking you to wire dependencies by hand.
Keep the public shape domain-first
Section titled “Keep the public shape domain-first”The consumer of Cart does not need to know about #items.
cart.items;cart.itemCount;cart.totalCents;That is the pattern to prefer: mark root state, then expose domain-shaped JavaScript above it.
Preview resources
Section titled “Preview resources”Some state needs more than a value. It needs setup, sync, and cleanup.
A Resource keeps the same private-state and domain-shaped-return pattern, but
adds lifecycle hooks. This example is a preview; framework adapters normally
schedule resources for you.
import { reactive } from "@starbeam/collections";import { Resource } from "@starbeam/universal";
const Clock = Resource(({ on }) => { const clock = reactive.object({ now: Date.now() });
on.sync(() => { clock.now = Date.now();
const timer = setInterval(() => { clock.now = Date.now(); }, 1000);
return () => clearInterval(timer); });
return clock;});The resource returns an ordinary object with a now property. The lifecycle work
is attached to the resource, not pushed through every caller.
Legible to humans and agents
Section titled “Legible to humans and agents”Starbeam does not ask an AI agent to translate every domain idea into a reactive DSL before the code can become reactive. The important abstractions stay local, inspectable JavaScript: modules, classes, methods, getters, collections, and explicit lifecycle setup, sync, and cleanup.
The developer still owns the design. Starbeam keeps the reactive boundary small enough that both the human and the agent can reason about the rest of the system as JavaScript.
Next steps
Section titled “Next steps”- Read Install Starbeam to choose packages for your app or library.
- Read Core concepts for the map of Starbeam’s model.
- Read Collections and objects for the root-state shapes that match ordinary JavaScript data.
- Read Resources and lifecycle when work needs setup, sync, or cleanup.
- Read Framework guides to see how adapters connect this model to React, Preact, Ember, Vue, and Svelte.