Skip to content

Realtime: Channels, Presence, Broadcast & Heartbeat

Hypern provides a high-performance realtime infrastructure built in Rust, designed for SSE and WebSocket patterns. It includes:

Component Purpose
ChannelManager Named pub/sub channels with topic-based routing
PresenceTracker Track who is online in each channel
RealtimeBroadcast Backpressure-aware fan-out with dedup
HeartbeatMonitor Liveness detection + SSE auto-reconnect helpers
RealtimeHub Convenience wrapper bundling all components

All heavy lifting runs in Rust (lock-free data structures, tokio broadcast channels); Python gets a clean, ergonomic API.


Quick Start

from hypern.realtime import RealtimeHub, BroadcastConfig

hub = RealtimeHub()

# Create a channel
hub.create_channel("chat:general")

# A user joins — subscribes + tracks presence + registers heartbeat
sub = hub.join("chat:general", "alice", {"name": "Alice", "status": "online"})

# Publish a message
hub.publish("chat:general", "Hello everyone!")

# Receive (non-blocking)
msg = sub.try_recv()  # "Hello everyone!"

# Get who's online
members = hub.get_presence("chat:general")

# When user disconnects
hub.disconnect("alice")

Channel / Topic System

ChannelManager

Manages named channels backed by Rust's tokio::broadcast for zero-copy fan-out.

from hypern.realtime import ChannelManager

manager = ChannelManager(default_buffer_size=256)

# Create channels
manager.create_channel("chat:general")
manager.create_channel("chat:random")
manager.create_channel("events:system", buffer_size=1024)

# Subscribe
sub = manager.subscribe("chat:general", "user-1")

# Publish
receivers = manager.publish("chat:general", "Hello!")

# Receive messages
msg = sub.try_recv()       # Non-blocking, returns None if empty
messages = sub.drain()      # Drain all pending messages

# JSON helpers
manager.publish_json("chat:general", {"type": "message", "text": "Hi"})

# Stats
stats = manager.get_stats("chat:general")
print(f"Subscribers: {stats.subscriber_count}, Messages: {stats.total_messages}")

# Cleanup
manager.unsubscribe("chat:general", "user-1")
manager.remove_channel("chat:general")

Topic Pattern Matching

Channels support pattern-based routing with wildcards:

Pattern Matches Example
chat:general Exact match only chat:general
chat:* Any single segment chat:general, chat:random
events:# Any number of segments events:user:login, events:system:alert:critical
from hypern.realtime import TopicMatcher

matcher = TopicMatcher()

# Subscribe to patterns
matcher.subscribe("chat:*", "user-1")      # All chat rooms
matcher.subscribe("events:#", "admin-1")    # All events (recursive)
matcher.subscribe("chat:general", "user-2") # Exact channel only

# Find who should receive a message
recipients = matcher.match_topic("chat:general")
# → ["user-1", "user-2"]

recipients = matcher.match_topic("events:user:login")
# → ["admin-1"]

# Static check
TopicMatcher.pattern_matches("chat:*", "chat:general")  # True
TopicMatcher.pattern_matches("chat:*", "events:foo")     # False

Publishing to Topic Patterns

manager = ChannelManager()
manager.create_channel("chat:general")
manager.create_channel("chat:random")

sub1 = manager.subscribe("chat:general", "u1")
sub2 = manager.subscribe("chat:random", "u2")

# Publish to all channels matching pattern
total = manager.publish_to_topic("chat:*", "Announcement!")
# Both sub1 and sub2 receive the message

Async Subscribe

import asyncio
from hypern.realtime import ChannelManager

manager = ChannelManager()
manager.create_channel("events")

async def handle_events():
    await manager.subscribe_async(
        "events", "worker-1",
        callback=lambda msg: print(f"Got: {msg}"),
        poll_interval=0.01,
    )

asyncio.create_task(handle_events())

Presence Tracking

Track which clients are connected to each channel, with metadata and diff-based updates.

from hypern.realtime import PresenceTracker

tracker = PresenceTracker()

# Track presence with metadata
tracker.track("room:lobby", "alice", {"name": "Alice", "status": "online"})
tracker.track("room:lobby", "bob", {"name": "Bob", "status": "away"})

# List members
members = tracker.list("room:lobby")
for m in members:
    print(f"{m.client_id}: {m.metadata}")

# Count
print(f"Members: {tracker.count('room:lobby')}")

# Get specific client
info = tracker.get("room:lobby", "alice")
print(f"Alice joined at: {info.joined_at}")

Metadata Updates

# Update a user's status
tracker.update("room:lobby", "alice", {"name": "Alice", "status": "away"})

# Touch last_seen (for heartbeat)
tracker.touch("room:lobby", "alice")

Diff-Based Updates

Instead of sending the full member list on every change, use diffs for efficient updates:

# After some joins/leaves have occurred:
diff = tracker.flush_diff("room:lobby")
if diff.has_changes():
    print(f"Joins: {[j.client_id for j in diff.joins]}")
    print(f"Leaves: {diff.leaves}")

# As a plain dict (ready for JSON broadcasting)
diff_dict = tracker.diff_as_dict("room:lobby")
# {"joins": [{"client_id": "alice", "metadata": {...}}], "leaves": ["bob"]}

Disconnect & Cleanup

# Remove from one channel
tracker.untrack("room:lobby", "alice")

# Remove from ALL channels (full disconnect)
channels_left = tracker.untrack_all("alice")
print(f"Alice left: {channels_left}")

# Evict stale presences (e.g., no heartbeat for 60s)
evicted = tracker.evict_stale(timeout_secs=60.0)
for channel, client_id in evicted:
    print(f"Evicted {client_id} from {channel}")

Backpressure-Aware Broadcast

The broadcast system wraps tokio::broadcast with configurable backpressure policies and optional message deduplication.

Basic Usage

from hypern.realtime import RealtimeBroadcast, BroadcastConfig, BackpressurePolicy

broadcast = RealtimeBroadcast()

# Create with default config
broadcast.create("notifications")

# Create with custom config
broadcast.create("alerts", BroadcastConfig(
    buffer_size=128,
    policy=BackpressurePolicy.DropOldest,  # or BackpressurePolicy.Error
    dedup_enabled=True,
    dedup_window=1000,
))

# Subscribe
rx = broadcast.subscribe("alerts")

# Send
count = broadcast.send("alerts", '{"type": "warning", "msg": "CPU high"}')
print(f"Delivered to {count} subscribers")

# Receive
msg = rx.try_recv()    # Non-blocking
msgs = rx.drain()       # Get all pending

# JSON helper
broadcast.send_json("alerts", {"type": "info", "msg": "Deployed v2.1"})

Backpressure Policies

Policy Behavior
BackpressurePolicy.DropOldest When no subscribers, silently drops. Lagging subscribers skip old messages.
BackpressurePolicy.Error Raises RuntimeError when no subscribers are active.
# Error policy — fail loudly when nobody listens
broadcast.create("critical", BroadcastConfig(policy=BackpressurePolicy.Error))
rx = broadcast.subscribe("critical")
broadcast.send("critical", "important")  # OK, 1 subscriber

# If no subscribers, this will raise RuntimeError

Message Deduplication

Prevent duplicate messages (useful for at-least-once delivery systems):

broadcast.create("events", BroadcastConfig(
    dedup_enabled=True,
    dedup_window=1000,  # Track last 1000 message IDs
))

rx = broadcast.subscribe("events")

broadcast.send("events", "event A", message_id="evt-1")
broadcast.send("events", "event A (dup)", message_id="evt-1")  # Skipped!
broadcast.send("events", "event B", message_id="evt-2")

msgs = rx.drain()  # ["event A", "event B"]

Multi-Channel Broadcast

broadcast.create("channel-a")
broadcast.create("channel-b")

rx_a = broadcast.subscribe("channel-a")
rx_b = broadcast.subscribe("channel-b")

# Send to multiple channels at once
results = broadcast.send_many(["channel-a", "channel-b"], "Hello all!")
# {"channel-a": 1, "channel-b": 1}

Statistics

stats = broadcast.stats("alerts")
print(f"Sent: {stats.total_sent}, Dropped: {stats.total_dropped}, Deduped: {stats.total_deduped}")

global_stats = broadcast.global_stats()
print(f"Total channels: {global_stats.channel_count}, Total sent: {global_stats.total_sent}")

Heartbeat / Auto-Reconnect

Server-side heartbeat monitoring for detecting dead connections, with SSE-specific helpers for keepalive and client-side auto-reconnect.

Basic Heartbeat

from hypern.realtime import HeartbeatMonitor, HeartbeatConfig

monitor = HeartbeatMonitor(HeartbeatConfig(
    interval_secs=15.0,   # Ping every 15 seconds
    timeout_secs=45.0,    # Dead after 45 seconds without pong
    max_retries=3,        # Evict after 3 timeouts
    sse_retry_ms=3000,    # SSE client retries after 3s
    send_keepalive=True,  # Enable keepalive for SSE
))

# Register clients
monitor.register("client-1")
monitor.register("client-2", last_event_id="evt-42")  # Resume SSE stream

# Record heartbeat activity
monitor.ping("client-1")  # We sent a ping
monitor.pong("client-1")  # Client responded

# Check for timeouts
timed_out = monitor.check_timeouts()
for client_id in timed_out:
    print(f"Client {client_id} timed out!")

# Get dead clients (exceeded max_retries)
dead = monitor.get_dead_clients()

# Evict dead clients
evicted = monitor.evict_dead()

# Cleanup
monitor.unregister("client-1")

SSE Auto-Reconnect Helpers

The heartbeat monitor generates SSE-compatible events for client-side reconnection:

# Generate SSE keepalive comment (prevents proxy timeouts)
comment = monitor.sse_keepalive_comment()
# ": keepalive\n\n"

# Generate SSE retry field (tells client to reconnect after N ms)
retry = monitor.sse_retry_field()
# "retry: 3000\n\n"

# Generate full heartbeat event (retry + comment)
heartbeat = monitor.sse_heartbeat_event()
# "retry: 3000\n: heartbeat\n\n"

# Create SSE events with auto-retry configured
event = monitor.make_sse_event(
    data="Hello",
    event="message",
    id="evt-43",
)
# SSEEvent with retry=3000 automatically set

Last-Event-ID Tracking (Resumable SSE)

# When client connects with Last-Event-ID header:
monitor.register("client-1", last_event_id="evt-100")

# Get the resume point
last_id = monitor.get_last_event_id("client-1")
if last_id:
    # Send events since last_id
    send_events_since(last_id)

# Update as events are sent
monitor.set_last_event_id("client-1", "evt-105")

Async Heartbeat Loop

Run a background heartbeat loop that automatically pings, detects timeouts, and evicts dead clients:

import asyncio
from hypern.realtime import HeartbeatMonitor, HeartbeatConfig

monitor = HeartbeatMonitor(HeartbeatConfig(interval_secs=10, timeout_secs=30))

async def on_ping(client_id):
    # Send SSE keepalive or WebSocket ping
    print(f"Pinging {client_id}")

async def on_timeout(client_id):
    print(f"Timeout: {client_id}")

async def on_dead(client_id):
    print(f"Evicted: {client_id}")

# Run as background task
asyncio.create_task(
    monitor.run_heartbeat_loop(
        on_ping=on_ping,
        on_timeout=on_timeout,
        on_dead=on_dead,
    )
)

Monitor Statistics

stats = monitor.stats()
print(f"Monitoring {stats.monitored_clients} clients")
print(f"Pings: {stats.total_pings}, Pongs: {stats.total_pongs}")
print(f"Timeouts: {stats.total_timeouts}, Currently dead: {stats.timed_out_clients}")

# Detailed per-client info
info = monitor.client_info()
# {"client-1": {"alive": "true", "retries": "0", "last_pong_ago_secs": "2.5"}}

RealtimeHub

A convenience wrapper that bundles all four components and provides coordinated join/leave/disconnect operations.

from hypern.realtime import RealtimeHub, HeartbeatConfig, BroadcastConfig

hub = RealtimeHub(
    channel_buffer_size=256,
    heartbeat_config=HeartbeatConfig(interval_secs=15, timeout_secs=45),
)

# Create a channel with broadcast support
hub.create_channel(
    "chat:general",
    broadcast_config=BroadcastConfig(buffer_size=128),
)

# Join = subscribe + track presence + register heartbeat
sub = hub.join("chat:general", "alice", {"name": "Alice"})

# Publish
hub.publish("chat:general", "Hello!")
hub.publish_json("chat:general", {"msg": "typed message"})

# Get presence
members = hub.get_presence("chat:general")
diff = hub.get_presence_diff("chat:general")

# Leave one channel
hub.leave("chat:general", "alice")

# Full disconnect (all channels)
channels_left = hub.disconnect("alice")

Complete Chat Room Example

from hypern import Hypern, Request, Response
from hypern.realtime import RealtimeHub, HeartbeatConfig, BroadcastConfig
import json, asyncio

app = Hypern()
hub = RealtimeHub(heartbeat_config=HeartbeatConfig(interval_secs=15))

# Create the chat room on startup
hub.create_channel("chat:main", broadcast_config=BroadcastConfig(buffer_size=256))


@app.get("/chat/join")
async def join_chat(request: Request, response: Response):
    user_id = request.query.get("user_id", "anonymous")
    sub = hub.join("chat:main", user_id, {"name": user_id})
    response.json({"status": "joined", "user_id": user_id})
    response.finish()


@app.post("/chat/send")
async def send_message(request: Request, response: Response):
    body = json.loads(request.body)
    user_id = body["user_id"]
    message = body["message"]
    hub.publish_json("chat:main", {
        "type": "message",
        "from": user_id,
        "text": message,
    })
    response.json({"status": "sent"})
    response.finish()


@app.get("/chat/members")
async def get_members(request: Request, response: Response):
    members = hub.presence.list_as_dicts("chat:main")
    response.json({"members": members})
    response.finish()


@app.get("/chat/leave")
async def leave_chat(request: Request, response: Response):
    user_id = request.query.get("user_id", "")
    hub.disconnect(user_id)
    response.json({"status": "left"})
    response.finish()

SSE Stream with Heartbeat Example

from hypern import Hypern, Request, Response, SSEEvent
from hypern.realtime import HeartbeatMonitor, HeartbeatConfig

app = Hypern()
monitor = HeartbeatMonitor(HeartbeatConfig(
    interval_secs=15,
    timeout_secs=45,
    sse_retry_ms=3000,
))


@app.get("/events")
async def sse_endpoint(request: Request, response: Response):
    client_id = request.query.get("client_id", "anon")
    last_event_id = request.headers.get("Last-Event-ID")

    # Register for heartbeat
    monitor.register(client_id, last_event_id=last_event_id)

    # Build SSE events
    events = []

    # If resuming, add events since last_event_id
    if last_event_id:
        # ... fetch missed events from your data store ...
        pass

    # Add the retry field so client auto-reconnects
    events.append(monitor.make_sse_event(
        data="connected",
        event="connect",
        id=f"evt-{client_id}-0",
    ))

    response.sse(events)
    response.finish()

API Reference

ChannelManager

Method Description
create_channel(name, buffer_size?, metadata?) Create a named channel
remove_channel(name) Remove a channel
has_channel(name) Check existence
subscribe(channel, client_id)Subscriber Subscribe to a channel
unsubscribe(channel, client_id) Unsubscribe
publish(channel, message)int Publish, returns receiver count
publish_json(channel, data)int Publish JSON
publish_to_topic(pattern, message)int Publish to matching channels
get_stats(channel)ChannelStats Get channel stats
list_channels()list[str] List all channels
get_subscribers(channel)list[str] Get subscriber IDs
subscribe_async(channel, client_id, callback) Async polling loop

Subscriber

Method/Property Description
try_recv()str \| None Non-blocking receive
drain()list[str] Drain all pending messages
channel_name Channel name
client_id Client identifier
received_count Messages received
missed_count Messages missed (lag)

TopicMatcher

Method Description
subscribe(pattern, client_id) Register pattern subscription
unsubscribe(pattern, client_id) Remove subscription
unsubscribe_all(client_id) Remove all subscriptions
match_topic(topic)list[str] Find matching client IDs
pattern_matches(pattern, topic) Static pattern check

PresenceTracker

Method Description
track(channel, client_id, metadata?)PresenceInfo Track presence
untrack(channel, client_id) Remove from channel
untrack_all(client_id)list[str] Remove from all channels
update(channel, client_id, metadata) Update metadata
touch(channel, client_id) Update last_seen
list(channel)list[PresenceInfo] List members
get(channel, client_id)PresenceInfo Get specific
count(channel)int Member count
flush_diff(channel)PresenceDiff Get incremental diff
evict_stale(timeout_secs) Remove inactive
list_as_dicts(channel) JSON-ready member list
diff_as_dict(channel) JSON-ready diff

RealtimeBroadcast

Method Description
create(name, config?) Create broadcast channel
remove(name) Remove channel
subscribe(name)BroadcastSubscriber Subscribe
send(name, message, message_id?)int Send message
send_json(name, data, message_id?)int Send JSON
send_many(names, message)dict Multi-channel send
stats(name)BroadcastStats Channel stats
global_stats()BroadcastStats All channels stats

HeartbeatMonitor

Method Description
register(client_id, last_event_id?) Start monitoring
unregister(client_id) Stop monitoring
ping(client_id) Record ping sent
pong(client_id) Record pong received
check_timeouts()list[str] Get timed-out clients
is_alive(client_id)bool Check liveness
evict_dead()list[str] Remove dead clients
set_last_event_id(client_id, id) Set SSE resume point
get_last_event_id(client_id)str Get SSE resume point
sse_keepalive_comment()str SSE keepalive string
sse_retry_field()str SSE retry field string
sse_heartbeat_event()str Full SSE heartbeat
make_sse_event(data, event?, id?)SSEEvent Create event with retry
run_heartbeat_loop(on_ping?, on_timeout?, on_dead?) Async loop
stats()HeartbeatStats Monitor statistics