Clients never write state. They call commands, and the host runs them. A command is a typed function call with arguments and a result, so the client gets autocomplete and the host keeps every rule in one place.
The screen doesn't wait. A client can predict a command's effect and show it at once. When the host's answer arrives, the prediction is replaced by the real state. If the host says no, the prediction disappears.
Try it
Two clients and one host, running in this page. Send +1 from A, then +1 from B. Each client shows its own +1 while the host still holds 0. Then apply, reject and return results, and watch each screen settle.
Client A
connectingClient B
connectingOne shared host
Command log (0)
Write a command
A handler can reject with a reason. Every write it made before that stands.
The client predicts with a plain function. It runs again whenever fresh state arrives, so it never drifts.
const { state, commands } = useStatewire<{ count: number } | undefined, Commands>({
transport: StatewireHttp({ url: "/counters/main" }),
optimistic: (draft, command) => {
if (command.method === "increment") draft.count += 1;
},
});
const { count } = await commands.increment();Long commands
A command can keep running after its first effect. Mark the effect as applied, then stream. The caller's promise resolves when the handler returns; everyone sees the text as it grows.
Other commands keep running while one streams, so a second client can send its own in the middle of a reply.