A few rules, applied consistently. The unit of composition is the
machine. Every machine declares where it runs (app scope
or per session), what it reads, what it emits, and how it
transitions.
Templates read machine state through explicit slots, and events
round-trip to the server. The server diffs and returns just the
slots that changed.
/ Machines, declared
// machines/cart.ts
import { defineMachine } from '@statorjs/stator/server'
import ProductsMachine from './products.ts'
export default defineMachine({
name: 'CartMachine',
lifecycle: 'session', // 'app' | 'session'
reads: [ProductsMachine], // declared dependencies
context: { items: [] },
initial: 'idle',
states: {
idle: {
on: {
ADD_ITEM: { actions: 'addItem', emit: 'ITEM_ADDED' },
},
},
},
actions: {
addItem: (ctx, ev, { reads }) => {
// The canonical price is read from ProductsMachine on the server.
// The wire event carries only productId.
const product = reads.ProductsMachine.byId(ev.productId)
if (!product) return
ctx.items.push({
productId: ev.productId,
unitPrice: product.price,
quantity: 1,
})
},
},
emits: {
ITEM_ADDED: { payload: (ctx) => ({ items: ctx.items }) },
},
selectors: {
itemCount: (ctx) => ctx.items.reduce((s, i) => s + i.quantity, 0),
},
})
Templates read machine state through explicit slot helpers. A
read call registers a binding the server can
diff against on later transitions.
// templates/header.ts
import { html, read } from '@statorjs/stator/template'
export default function header(cart) {
return html`<a href="/cart">Cart (${read(cart, c => c.itemCount)})</a>`
}
/ The wire edge
Every event arriving at the server is shape-validated before any
machine sees it. The framework knows which machine the event
targets, which route the client is on, and which slots that route's
templates registered.
A single POST produces a small JSON patch list, set this slot's
text, replace that list's innerHTML, changes this element's class. etc.
The ~1kB client script applies each patch by element id.
No call-arbitrary-server-function or exposed RPC surface. Just send an
event to a machine and let the machine decides what to do.