Blog
Engineering

Realtime by default, not as an afterthought

How Lunora turns every query into a live subscription, with optimistic updates and an offline queue baked in.

Realtime by default, not as an afterthought
DBDaniel Bannert1 min read

Most backends bolt real-time on after the fact. You stand up a separate WebSocket server, add a pub/sub bus, and write a pile of glue to keep clients in sync with the database. It works, but now you've got a second system to reason about, and it drifts out of sync with your actual data more often than anyone likes to admit.

Lunora doesn't do that. Every query is a subscription.

One mental model

You read data with useQuery. That read is the subscription. Any mutation that touches the same tables re-runs the affected queries and pushes the fresh result to every client watching them. No channels to wire up, no cache to invalidate by hand.

const messages = useQuery(api.messages.list, { roomId });
const send = useMutation(api.messages.send);

That's the whole surface area. Reactivity isn't a feature you opt into here. It's just how reads work.

Optimistic, and fine offline

Mutations apply on the client right away and reconcile when the server confirms. Lose the network mid-write and the change gets queued durably and retried, keyed by a client id so a flaky connection never double-sends.

How it actually works

Each shard is a SQLite database living inside a Durable Object at the edge, with optimistic concurrency control and hibernated WebSocket subscriptions. An idle connection costs you almost nothing, which is what makes live updates cheap and consistent without a central server fanning everything out.

There's a lot more under the hood. I'll go deeper on the internals in future posts, so follow along.