Expand description
Realtime updates: WebSocket channels on a Durable Object, HTML broadcasts for htmx (feature realtime).
Works like Rails’ Action Cable and Turbo Streams: browsers subscribe to a
named channel over a WebSocket, and handlers or jobs
broadcast HTML fragments (or JSON) to every
subscriber.
use axum::{Router, extract::{Path, State}, response::Response, routing::{get, post}};
use ocre::{Ctx, Error, Result, realtime::{self, WebSocketUpgrade}};
// src/realtime.rs: who may listen to which channel (`ocre g scaffold ... --realtime` writes it).
async fn connect(State(ctx): State<Ctx>, Path(channel): Path<String>, upgrade: WebSocketUpgrade) -> Result<Response> {
match channel.as_str() {
"posts" => {}
_ => return Err(Error::NotFound),
}
upgrade.connect(&ctx, &channel).await
}
// Any handler or job: the new row goes to the top of every open index page.
async fn create(State(ctx): State<Ctx>) -> Result<&'static str> {
let row_html = "<tr id=\"post_1\"><td>Hello</td></tr>";
realtime::broadcast(&ctx, "posts", &realtime::prepend("posts", row_html)).await?;
Ok("created")
}
fn routes() -> Router<Ctx> {
Router::new().route("/realtime/{channel}", get(connect)).route("/posts", post(create))
}In the page, htmx’s WebSocket extension connects and swaps each message
into the element with the same id (hx-swap-oob), without custom
JavaScript. Load the extension in the layout’s <head>, after htmx: a
page that loaded it itself would not connect when reached through an
hx-boost link.
<!-- templates/layout.html, in <head> -->
<script src="https://unpkg.com/htmx-ext-ws@2.0.4/dist/ws.js" crossorigin="anonymous"></script>
<!-- the page -->
<div hx-ext="ws" ws-connect="/realtime/posts">
<table><tbody id="posts">...<tr id="post_1">...</tr></tbody></table>
</div>Messages: an element with an id replaces the page element with that id;
append, prepend,
update and remove
build the other swaps. One message may hold several of them.
How it runs: one Durable Object of class OcreChannel (binding
CHANNELS) per channel name holds the channel’s WebSockets with the
WebSocket Hibernation API, so it is evicted from memory, and costs no
duration, between broadcasts while browsers stay connected. It stores
nothing.
Free plan (see the README’s Realtime section): each connection (and
reconnection) and each broadcast is one Durable Object request (100,000 a
day); messages sent to browsers are free; a connection is also one Worker
request, while a broadcast is a subrequest of the request that sends it.
Needs Ocre’s realtime feature and, in cloudflare.config.ts, the binding and
the SQLite-backed OcreChannel export (see OcreChannel).
API-only apps can use the same pieces and broadcast JSON.
Rails’ Action Cable pieces map as follows: the connect handler is the
connection (identified_by is WebSocketUpgrade::identified_by, its
checks and Result are the callbacks and rescue_from), the channel
name and the route’s query string are the channel params, client actions
(perform) are ordinary routes that broadcast, and
WebSocketUpgrade::rebroadcast relays what clients send to the other
subscribers. dev_routes lists recent broadcasts for tests.
Structs§
- Ocre
Channel - The Durable Object class behind each realtime channel, exported by Ocre under the name
OcreChannel. - WebSocket
Upgrade - Axum extractor for a WebSocket handshake (
Upgrade: websocket), finished withconnect.
Constants§
- CHANNELS_
BINDING - Name of the Durable Object binding holding the channels, declared in cloudflare.config.ts.
- CHANNEL_
CLASS - Name of the Durable Object class Ocre exports for channels (
OcreChannel). - LOG_
PREFIX - Prefix of every line Ocre logs about realtime, e.g. in
ocre devoutput. - MAX_
CHANNEL_ LEN - Longest channel name, in bytes.
- MAX_
IDENTITY_ LEN - Longest identity given to
WebSocketUpgrade::identified_by, in bytes. - MAX_
REBROADCAST_ BYTES - Longest client message
WebSocketUpgrade::rebroadcastrelays, in bytes; longer ones are dropped.
Functions§
- append
- Builds a message that inserts
htmlat the end of the element with idtarget(htmxbeforeend). - broadcast
- Sends
message(an HTML fragment or JSON text) to every browser connected tochannel. - dev_
routes - Development endpoint listing recent broadcasts, served by
ocre devonly, for tests (Rails’assert_broadcasts). - prepend
- Builds a message that inserts
htmlat the start of the element with idtarget(htmxafterbegin). - remove
- Builds a message that removes the element with id
idfrom the page (htmxdelete). - update
- Builds a message that replaces the contents of the element with id
target(htmxinnerHTML).