Pure SSE frame parser. No IO, no processes, no external dependencies.
An SSE frame is a sequence of field: value lines terminated by a blank line (\n\n).
Recognised fields: event, data, id, retry. Lines starting with : are comments.
Multiple data: lines within one frame are concatenated with "\n".
Summary
Functions
Parse one complete raw frame string (without the trailing "\n\n") into a %Frame{}.
Split a byte buffer on the SSE frame delimiter ("\n\n").
Types
@type t() :: %ReqServerSentEvents.Frame{ comments: [String.t()], data: String.t() | nil, event: String.t() | nil, id: String.t() | nil, retry: non_neg_integer() | nil }
Functions
Parse one complete raw frame string (without the trailing "\n\n") into a %Frame{}.
Frames with no data: field are returned as-is — the caller decides whether to
dispatch or discard them. Unknown field names are silently ignored per the SSE spec.
Examples
iex> ReqServerSentEvents.Frame.parse("event: ping\ndata: {}")
%ReqServerSentEvents.Frame{event: "ping", data: "{}"}
iex> ReqServerSentEvents.Frame.parse(": keepalive")
%ReqServerSentEvents.Frame{comments: ["keepalive"]}
Split a byte buffer on the SSE frame delimiter ("\n\n").
"\r\n" sequences are normalised to "\n" before splitting, so servers
that send CRLF line endings (e.g. "\r\n\r\n" frame delimiters) are handled
transparently. The returned frame strings use "\n" throughout. A leading
UTF-8 byte order mark ("\uFEFF") is stripped per the SSE spec.
Returns {complete_frames, leftover} where complete_frames is a list of raw
frame strings (without the trailing "\n\n") and leftover is the remaining
bytes that have not yet formed a complete frame.
Examples
iex> ReqServerSentEvents.Frame.split("data: hello\n\n")
{["data: hello"], ""}
iex> ReqServerSentEvents.Frame.split("data: hello\r\n\r\n")
{["data: hello"], ""}
iex> ReqServerSentEvents.Frame.split("data: partial")
{[], "data: partial"}