Examples
Four runnable applications in the
examples/
directory use a Gleam/BEAM backend and a browser frontend. The repository also
contains the live-poll tutorial, load-test server, and shared example helpers.
The installed package does not include the examples. Clone the repository:
git clone https://github.com/tylerbutler/beryl.gitcd berylShowcase
Section titled “Showcase”Source: examples/showcase
This example uses one socket runtime and three topic families. The
beryl/channel API combines cursor, chat room, and shared document handlers in
one supervised socket system. It does not need a socket-wide model or a custom
router.
cd examples/showcasegleam run# Open http://localhost:8000Features used
Section titled “Features used”| beryl feature | How it's used |
|---|---|
| Channel composition | One channel.child_spec handler table owns cursor:*, room:*, and the document: prefix |
| Per-channel state | Each joined topic keeps its own private state; the layer prunes it on close |
| Join and close actions | Join and termination callbacks return actions that beryl runs in list order |
| Per-pattern rate limits | with_topic_rate gives cursor traffic a higher limit than chat and document traffic |
| Shared presence | One ETS-backed session-presence tracker serves cursors and chat, publishing snapshots asynchronously |
| Single WebSocket endpoint | Every embedded app shares /socket/websocket |
How it fits together
Section titled “How it fits together”Browser (Phoenix JS clients across /cursors, /chat, /docs) │ WebSocket (Phoenix wire protocol)Server (Gleam) ├── Mist HTTP — serves the landing page and each example UI ├── beryl/channel — handler table owning cursor:*, room:*, document: ├── per-channel state — private to each joined topic ├── session presence — shared across cursors and chat └── groups + doc store — example-specific stateCollaborative cursors
Section titled “Collaborative cursors”Source: examples/cursors
Move your mouse to send its position in real time. Open the app in multiple browser tabs to see other cursors.
cd examples/cursorsgleam run# Open http://localhost:8000 in multiple browser tabsFeatures used
Section titled “Features used”| beryl feature | How it's used |
|---|---|
| Raw dispatch | One socket-wide model handles the cursor:* topic family |
| Topic routing | The update function matches cursor:* directly with beryl/topic |
| Session presence (ETS) | The example-local session_presence tracker stores connected users and their username + color metadata in ETS |
broadcast_from | Sends cursor moves to all other clients, excluding the sender |
| Rate limiting | beryl.with_message_rate throttles high-frequency cursor events |
| WebSocket transport | mist_transport.upgrade handles Phoenix-compatible WebSocket requests |
| Phoenix JS client | Frontend uses the official phoenix package over the standard wire protocol |
How it fits together
Section titled “How it fits together”Browser (vanilla JS + Phoenix JS client) │ WebSocket (Phoenix wire protocol)Server (Gleam) ├── Mist HTTP — serves HTML + static files ├── beryl app dispatch — cursor:* topics ├── example session_presence — ETS-backed user tracking └── beryl pubsub — broadcast_from cursor positionsChat rooms
Section titled “Chat rooms”Source: examples/chatrooms
A multi-room chat app with authentication, join rejection, typing indicators, and message acknowledgment.
cd examples/chatroomsgleam run# Open http://localhost:8001?token=beryl-demo in multiple browser tabsFeatures used
Section titled “Features used”This example uses parts of the beryl API that the cursor example does not use:
| beryl feature | How it's used |
|---|---|
on_connect auth | Token query param validated before WebSocket upgrade is accepted |
channel.reject | Rooms reject joins when full (20-user cap) or when room doesn't exist |
channel.reply_ok | Delivery of new_msg confirmed with an ok-status phx_reply |
| Coded error replies | Empty messages rejected with HTTP-style code 422 in the reply payload |
| Groups | Three rooms (general, random, help) organised in a named group |
| Session presence (ETS) | The example-local session_presence tracker stores online users and typing metadata in ETS |
| System messages | "user joined" / "user left" broadcasts from join and termination actions |
| Multiple topics | lobby and room:* handlers compose in one channel.child_spec table |
| Rate limiting | with_join_rate (5/sec) and with_channel_rate (10/sec/channel) |
How it fits together
Section titled “How it fits together”Browser (vanilla JS + Phoenix JS client, token auth) │ WebSocket (Phoenix wire protocol, ?token=beryl-demo)Server (Gleam) ├── Mist HTTP — static files, /api/rooms ├── Mist WebSocket transport — on_connect validates token ├── beryl/channel — lobby + room:* handlers ├── beryl groups — "public" group → general, random, help └── example session_presence — ETS-backed online users + typing indicatorsChannel events
Section titled “Channel events”| Direction | Event | Purpose |
|---|---|---|
| Client → Server | new_msg | Send a chat message {text} |
| Client → Server | typing | Start typing indicator |
| Client → Server | stop_typing | Stop typing indicator |
| Server → Client | new_msg | Broadcast message {text, username, color, type, timestamp} |
| Server → Client | phx_reply (push ref) | Reply to a client push — used for both delivery acknowledgment and validation errors. The demo returns validation failures with channel.reply_ok, so their wire status is still ok; the response payload ({code, error}) represents the domain validation error. |
| Server → Client | presence_list | Updated online user list |
| Server → Client | typing | Typing indicator update |
Collaborative documents
Section titled “Collaborative documents”Source: examples/collab_docs
This shared document editor merges block state in each client with a CRDT. It uses beryl as an unordered real-time transport.
just depscd examples/collab_docs && gleam run# Open http://localhost:8002 in multiple browser tabsFeatures used
Section titled “Features used”| beryl feature | How it's used |
|---|---|
| Segment-shaped topics | The document:* handler claims the prefix, then validates each document:<tenant>:<document> topic on join |
| Client-side CRDT merge | Browser state uses lattice_core, lattice_maps, and lattice_registers with ORMap(MVRegister(String)) document blocks |
| Unordered realtime transport | beryl broadcasts document updates while the CRDT handles merge convergence |
| Late joiner cache | Server returns cached merged state in the join reply for new clients |
| Conflict resolution UI | Concurrent edits to the same block render explicit conflict cards with all versions |
How it fits together
Section titled “How it fits together”Browser (vanilla JS + Phoenix JS client + lattice CRDT packages) │ WebSocket (Phoenix wire protocol)Server (Gleam) ├── Mist HTTP — serves HTML + static files ├── beryl/channel — document:* handler with tenant/document validation ├── document cache — merged state for late joiners └── beryl pubsub — sends CRDT updates to subscribersChoose an example
Section titled “Choose an example”| Starting point | Go here |
|---|---|
| I want a minimal working example right now | Quick Start |
| I want to see live presence + cursors | examples/cursors |
| I want auth, join validation, or groups | examples/chatrooms |
| I want collaborative documents or CRDT conflicts | examples/collab_docs |
| I want to understand the dispatch API | Channels guide |
| I want to add presence to my app | Presence guide |
