# roar
[](https://hex.pm/packages/roar)
[](https://hexdocs.pm/roar/)
Lightweight distributed pub/sub for Gleam on Erlang with automatic cluster synchronization.
Roar enables process-local and cluster-wide message broadcasting with automatic scope isolation, supporting both streaming subscriptions and callback-based handlers.
## Features
- 🌍 **Distributed by default** - Messages automatically sync across all nodes in your cluster
- 🎯 **Scope isolation** - Multiple independent pub/sub systems in the same cluster
- 📦 **Simple API** - Subscribe with callbacks or buffered streams
- 🛡️ **Memory safe** - Configurable buffer capacity prevents runaway memory growth
- ⚡ **BEAM native** - Built on Erlang's distribution primitives
## Installation
Add `roar` to your Gleam project:
```sh
gleam add roar
```
## Quick Start
```gleam
import roar
pub fn main() {
// Create a router with buffer capacity and scope
let router = roar.new(capacity: 100, scope: "my_app")
// Subscribe to a topic
let sub = roar.subscribe(router, "events")
// Publish a message (reaches ALL nodes in "my_app" scope)
roar.publish(router, "events", "Hello, cluster!")
// Receive messages
case sub.receive() {
Ok(message) -> io.println(message)
Error(Nil) -> io.println("No messages")
}
sub.cancel()
}
```
## Usage
### Callback-based subscriptions
```gleam
let cancel = roar.on(router, "notifications", fn(msg) {
io.println("Received: " <> msg)
})
roar.publish(router, "notifications", "New user signed up!")
cancel()
```
### Buffered subscriptions
```gleam
let sub = roar.subscribe(router, "events")
roar.publish(router, "events", "Event 1")
roar.publish(router, "events", "Event 2")
// Messages are buffered, read at your own pace
let assert Ok("Event 1") = sub.receive()
let assert Ok("Event 2") = sub.receive()
sub.cancel()
```
### Multiple topics
```gleam
let router = roar.new(capacity: 50, scope: "chat")
let messages = roar.subscribe(router, "messages")
let alerts = roar.subscribe(router, "alerts")
roar.publish(router, "messages", "Hello!")
roar.publish(router, "alerts", "Server restarting")
// Each subscription only receives its topic's messages
```
### Scope isolation
```gleam
// Different scopes don't interfere with each other
let app_router = roar.new(capacity: 100, scope: "app")
let admin_router = roar.new(capacity: 100, scope: "admin")
roar.subscribe(app_router, "events") // Only sees "app" scope
roar.subscribe(admin_router, "events") // Only sees "admin" scope
```
## How It Works
Roar uses [Whisper](https://hexdocs.pm/whisper/) for local pub/sub and Erlang's `pg` (process groups) for cluster-wide distribution. When you publish a message:
1. **Local delivery** - Delivered immediately to subscribers on the same node
2. **Remote delivery** - Forwarded to all nodes in the same scope via process groups
3. **No duplicates** - Each subscriber receives exactly one copy
The `capacity` parameter protects against memory issues by limiting buffered messages per subscription. If a subscriber is too slow, old messages are dropped to prevent memory exhaustion.