Skip to content

beryl/channel

Channel - Topic-based message callbacks

Channels handle real-time communication for topic patterns. Each channel defines how to handle joins, incoming messages, and cleanup.

pub type RoomAssigns {
RoomAssigns(user_id: String, room_id: String)
}
pub fn new() -> Channel(RoomAssigns, info) {
channel.new(join)
|> channel.with_handle_in(handle_in)
|> channel.with_terminate(terminate)
}
fn join(topic, payload, socket) {
let assigns = RoomAssigns(user_id: "...", room_id: "...")
channel.JoinOk(reply: None, socket: socket.set_assigns(socket, assigns))
}

Channel behavior definition

Type parameters:

  • assigns: Socket state type for this channel
  • info: Server-originated/internal message type delivered to handle_info (see beryl.send_info). Channels that do not use handle_info leave this parameter generic.
pub type Channel(a, b)

Result of handling an incoming message

pub type HandleResult(a) {
NoReply(socket: socket.Socket(a))
Reply(
event: String,
payload: json.Json,
socket: socket.Socket(a)
)
ReplyError(
payload: json.Json,
socket: socket.Socket(a)
)
Push(
event: String,
payload: json.Json,
socket: socket.Socket(a)
)
Stop(reason: StopReason)
}

Continue without sending a reply

event: String, payload: json.Json, socket: socket.Socket(a) )`

Send a successful reply to the client in response to their message.

When returned from handle_in, this is encoded as a Phoenix phx_reply with "status": "ok", tied to the original client ref — the event field is ignored by the coordinator. When returned from handle_info (where no client ref exists), it is sent as a push using event as the event name.

The status is always "ok": Reply("error", payload, socket) reaches the client's push.receive("ok", ...) hook, not receive("error", ...). Use ReplyError to signal failure.

payload: json.Json, socket: socket.Socket(a) )`

Send an error reply to the client in response to their message ("status": "error" in Phoenix framing, delivered to the client's push.receive("error", ...) hook).

Only meaningful from handle_in for messages carrying a client ref; from handle_info/handle_binary (where no ref exists) the reply is dropped with a warning.

event: String, payload: json.Json, socket: socket.Socket(a) )`

Push a message to the client (server-initiated)

Stop the channel with a reason

Result of joining a channel

pub type JoinResult(a) {
JoinOk(
reply: option.Option(json.Json),
socket: socket.Socket(a)
)
JoinError(reason: json.Json)
}

reply: option.Option(json.Json), socket: socket.Socket(a) )`

Join succeeded, optionally send a reply payload

Join failed with error payload

Why a channel is stopping.

Delivered to every channel's terminate callback. Match with a catch-all (_) arm: new stop reasons may be added in minor releases.

pub type StopReason {
Normal
Shutdown
HeartbeatTimeout
Errored(String)
}

Normal shutdown (client left or disconnected cleanly)

Server-initiated shutdown

Client failed to send heartbeat within the configured timeout

The channel stopped because of an error (named Errored so importing it unqualified does not shadow the prelude's Result Error constructor)

Decode an inbound channel payload into an application type.

pub fn decode_payload(
dynamic.Dynamic,
decode.Decoder(a)
) -> Result(a, List(decode.DecodeError))

Create a simple error response

pub fn error(String) -> json.Json

Create an error response with code

pub fn error_with_code(
Int,
String
) -> json.Json

Create a new channel with just a join callback.

Other callbacks can be added using the with_* functions.

pub fn new(fn(String, dynamic.Dynamic, socket.Socket(a)) -> JoinResult(a)) -> Channel(a, b)

Add a binary message callback

pub fn with_handle_binary(
Channel(a, b),
fn(BitArray, socket.Socket(a)) -> HandleResult(a)
) -> Channel(a, b)

Add an incoming message callback

pub fn with_handle_in(
Channel(a, b),
fn(String, dynamic.Dynamic, socket.Socket(a)) -> HandleResult(a)
) -> Channel(a, b)

Add a server-originated OTP message callback.

The callback receives the typed info value sent via beryl.send_info. Reply results are sent as pushes because server-originated messages do not have a client message ref to reply to.

pub fn with_handle_info(
Channel(a, b),
fn(b, socket.Socket(a)) -> HandleResult(a)
) -> Channel(a, b)

Add a terminate callback for cleanup

pub fn with_terminate(
Channel(a, b),
fn(StopReason, socket.Socket(a)) -> Nil
) -> Channel(a, b)