Packages

Distributed pub/sub for Gleam with automatic cluster synchronization across BEAM nodes

Current section

Files

Jump to
roar src roar.gleam
Raw

src/roar.gleam

import whisper
/// A distributed pub/sub router that synchronizes messages across a cluster.
///
/// Roar combines local message delivery (via Whisper) with automatic distribution
/// to all nodes in the same scope. Messages published on one node are automatically
/// forwarded to subscribers on all other nodes sharing the same scope identifier.
///
/// ## Example
///
/// ```gleam
/// let router = roar.new(capacity: 100, scope: "my_app")
/// let sub = roar.subscribe(router, "events")
///
/// roar.publish(router, "events", "Hello from node 1!")
/// // Subscribers on ALL nodes in "my_app" scope receive this message
/// ```
pub opaque type Roar(a) {
Roar(local: whisper.Whisper(a), scope: String)
}
/// Create a new distributed pub/sub router.
///
/// - `capacity`: Maximum number of messages buffered per subscription
/// - `scope`: Unique identifier for this router's cluster scope. Only routers
/// with matching scopes will exchange messages across nodes.
///
/// ## Example
///
/// ```gleam
/// let router = roar.new(capacity: 50, scope: "chat_system")
/// ```
pub fn new(capacity capacity: Int, scope scope: String) -> Roar(a) {
Roar(local: whisper.new(capacity), scope: scope)
}
/// Register a callback function to be invoked when messages are published to a topic.
///
/// The callback is triggered for messages published locally and from remote nodes
/// in the same scope. Returns a cancellation function to stop listening.
///
/// ## Example
///
/// ```gleam
/// let cancel = roar.on(router, "notifications", fn(msg) {
/// io.println("Received: " <> msg)
/// })
///
/// // Later, stop listening
/// cancel()
/// ```
pub fn on(roar: Roar(a), topic: String, listener: fn(a) -> Nil) -> fn() -> Nil {
let local_cancel = whisper.on(roar.local, topic, listener)
let distributed_cancel = platform_on_distributed(roar.scope, topic, listener)
fn() {
local_cancel()
distributed_cancel()
}
}
@external(erlang, "roar_ffi", "on_distributed")
fn platform_on_distributed(
scope: String,
topic: String,
listener: fn(a) -> Nil,
) -> fn() -> Nil
/// Subscribe to a topic and receive messages through a buffered subscription.
///
/// Messages published locally or from remote nodes in the same scope are queued
/// in the subscription buffer. Use the returned subscription's `receive` function
/// to consume messages, and `cancel` to unsubscribe.
///
/// ## Example
///
/// ```gleam
/// let sub = roar.subscribe(router, "events")
///
/// case sub.receive() {
/// Ok(message) -> io.println("Got: " <> message)
/// Error(Nil) -> io.println("No messages available")
/// }
///
/// sub.cancel()
/// ```
pub fn subscribe(roar: Roar(a), topic: String) -> whisper.Subscription(a) {
let sub = whisper.subscribe(roar.local, topic)
platform_subscribe_distributed(roar.scope, topic, roar.local)
sub
}
@external(erlang, "roar_ffi", "subscribe_distributed")
fn platform_subscribe_distributed(
scope: String,
topic: String,
local_whisper: whisper.Whisper(a),
) -> Nil
/// Publish a message to all subscribers of a topic across the cluster.
///
/// The message is delivered to:
/// - All local subscribers on this node
/// - All subscribers on remote nodes in the same scope
///
/// ## Example
///
/// ```gleam
/// roar.publish(router, "alerts", "System maintenance in 5 minutes")
/// // All subscribers across all nodes receive this message
/// ```
pub fn publish(roar: Roar(a), topic: String, message: a) -> Nil {
whisper.publish(roar.local, topic, message)
platform_publish_distributed(roar.scope, topic, message)
}
@external(erlang, "roar_ffi", "publish_distributed")
fn platform_publish_distributed(scope: String, topic: String, message: a) -> Nil