Code-first · Effect-native
Write the machine. Watch it run.
State machines for Effect. Model checkouts, syncs, and retries as explicit states and typed events — impossible states can't happen, every transition has a name, and the definition you author — or your agent writes — is the one Studio shows you running.
const definition = checkout.define(
{
id: "checkout",
idempotencyKey: () => "checkout",
initial: () => ({ _tag: "Browsing", items: 0 }),
},
{
Browsing: checkout.state({
AddItem: { target: "Browsing", reduce: … },
RemoveItem: { target: "Browsing", reduce: … },
BeginCheckout: { branches: [{ when: cartHasItems, target: "Checkout" }] },
}),
Checkout: checkout.state({ SubmitOrder: …, BackToShop: … }),
PlacingOrder: checkout.invoke({
name: "Orders.place",
success: Schema.String, error: PaymentDeclined,
// an Effect that needs a service — its Layer still comes later
effect: (state) => Effect.flatMap(Orders, ({ place }) => place(state.items)),
onSuccess: { target: "Ordered", reduce: ({ value }) => ({ orderId: value }) },
onFailure: { target: "PaymentFailed" }, // typed PaymentDeclined
}),
PaymentFailed: checkout.state({ RetryPayment: …, BackToShop: … }),
Ordered: checkout.final(),
},
)
// a scoped Effect — Orders is inferred, provided by a Layer
const handle = yield* definition.run({}).pipe(
Effect.provide(OrdersLive),
Effect.provide(MachineEngine.layerMemory()),
)Every click below is an event.
The Bug Emporium is an ordinary Effect app: one definition.run, one explicit MachineEngine Layer, and an embedded Studio bound to the live handle. Buy some bugs — get declined, retry — and the behavior map, snapshot, and semantic history follow. Move the history cursor to inspect an older step; the running shop never notices.
Built for Effect, not adapted to it.
Dependencies stay services provided by Layers. Expected failures stay typed. Cancellation uses Scope and fibers, retry uses native Schedule. The handle is Effect-native: no Promise methods, no global runtime. Every definition runs through an explicit MachineEngine backed by the MachineStore you choose.
pnpm add effect-state-machine effectWhat the definition actually is
One immutable value describes input, states, events, transitions, timers, regions, invoked work, and children. Tooling reads that value — it never executes Effects to discover the graph. Agents make code cheap to write; the graph keeps it cheap to review.
Immutable authored value. Inspectable without running Effects.
Scoped Effect. The engine and invoked-work requirements remain explicit Layers.
snapshot, changes, send, can, completion, inspection.
Embed it in React or connect browser and Node machines to the standalone app. Remote attachment is scoped, lazy, and inert when Studio is closed.
The definition projected for humans reviewing code — yours or your agent's. Never a second editable source of truth.
Store-backed checkpoints retain state, mailbox order, absolute timer deadlines, dispatch keys, and encoded work outcomes across process loss.