Broadcast Topics (pub/sub)
A topic is a typed, in-process broadcast channel that decouples where a value comes from from who streams it out. Any handler publishes with Name.send(v); streaming handlers — gRPC server/bidi streams or HTTP Server-Sent Events — subscribe with for x in Name. It's the transport-independent generalization of a WebSocket hub's broadcast.
The classic use is a value posted to one endpoint that fans out to open streams — a gRPC feed that emits whenever an HTTP POST arrives, or vice-versa:
type Order = { id: int @1, item: string @2 }
topic Orders -> Order # a typed broadcast topic
route POST "/orders" (body o: Order) -> string {
Orders.send(o) # publish — fans out to every subscriber
respond "queued"
}
listen g { port 50051 }
service feed.Feed on g {
rpc Watch(_: Order) -> Stream<Order> {
for o in Orders { emit o } # subscribe: blocks, emits each published Order
}
}
route GET "/live" -> string { # ...the same topic over HTTP SSE
type "text/event-stream"
for o in Orders { emit "data: {o.id}:{o.item}\n\n" }
}A POST /orders is now delivered to every open Watch gRPC stream and every /live SSE client at once.
Semantics
- Broadcast (fan-out). Every subscriber receives every message published after it subscribed. There's no replay — a new subscriber only sees messages sent from then on.
Name.send(v)publishes one value;vmust match the topic's message type. It's valid in any handler — routes, gRPC handlers, hub methods, andeverytimers — and never fails (see backpressure).for x in Namesubscribes and yields each message. It blocks between messages and ends when the client disconnects, so it belongs in a streaming handler (a gRPCstreamrpc or an SSE route thatemits).xis one message, decoded into the request's memory.- Backpressure is drop-oldest. Each subscriber has a bounded buffer; if it can't keep up, the oldest undelivered message is dropped (and counted) rather than blocking the publisher — so one slow gRPC client never stalls an HTTP
POST. This mirrors how a hub skips a stuck WebSocket consumer. - Disconnect handling. A subscription ends when the peer closes the connection: detected immediately on the next
emit(a failed write) and, for an idle topic, within a few seconds by a background sweep that probes liveness.
Scope
Topics are in-process and in-memory (a single server instance), with at-most-once delivery — ideal for live feeds, notifications, and cross-handler events within one service. They are not durable or cross-instance: a restart drops undelivered messages, and two instances don't share a topic. For durable or multi-instance messaging, put a broker (NATS/Kafka) between instances — the same topic handler code is the natural front for it.
Like hubs and timers, topics run on the threaded driver (not --green yet).