Current section

Files

Jump to
ssevents src ssevents@event.erl
Raw

src/ssevents@event.erl

-module(ssevents@event).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ssevents/event.gleam").
-export([comment_text_of/1, comment/1, comment_item/1, event/2, id/2, retry/2, new/1, from_parts/4, message/1, named/2, data/2, name_of/1, data_of/1, id_of/1, retry_of/1, event_item/1]).
-export_type([event/0, comment/0, item/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Event and item domain values.\n"
"\n"
" `Event` and `Comment` are opaque so the package can evolve their\n"
" representation without a breaking change. Construct via `new`,\n"
" `from_parts`, and the builder helpers (for `Event`) or `comment`\n"
" (for `Comment`). `Item` stays transparent so callers and helper\n"
" modules can pattern match on whether a stream element is an event\n"
" or a comment.\n"
).
-opaque event() :: {event,
gleam@option:option(binary()),
binary(),
gleam@option:option(binary()),
gleam@option:option(integer())}.
-opaque comment() :: {comment, binary()}.
-type item() :: {event_item, event()} | {comment_item, comment()}.
-file("src/ssevents/event.gleam", 52).
?DOC(" Extract the sanitised comment text.\n").
-spec comment_text_of(comment()) -> binary().
comment_text_of(C) ->
erlang:element(2, C).
-file("src/ssevents/event.gleam", 120).
?DOC(
" Strip CR (U+000D), LF (U+000A), and NUL (U+0000) from `value`.\n"
"\n"
" CR / LF inside an `event` or `id` value cannot survive the SSE wire\n"
" format — both are line terminators per WHATWG SSE §9.2.5, so a\n"
" literal CR / LF inside the value would split the field across two\n"
" lines on encode and the decoder would parse the post-LF tail as an\n"
" unrelated unknown field. NUL inside `id` is ignored by the decoder\n"
" per §9.2.6, breaking round-trip.\n"
"\n"
" Stripping silently at construction time is the same posture\n"
" `multipartkit/form.add_field` takes for the analogous header-injection\n"
" risk: it keeps `from_parts`, `event/2`, and `id/2` infallible while\n"
" guaranteeing that `decode(encode(x))` round-trips for any caller-built\n"
" `Event`.\n"
).
-spec sanitize_field_value(binary()) -> binary().
sanitize_field_value(Value) ->
_pipe = Value,
_pipe@1 = gleam@string:replace(_pipe, <<"\r\n"/utf8>>, <<""/utf8>>),
_pipe@2 = gleam@string:replace(_pipe@1, <<"\r"/utf8>>, <<""/utf8>>),
_pipe@3 = gleam@string:replace(_pipe@2, <<"\n"/utf8>>, <<""/utf8>>),
gleam@string:replace(_pipe@3, <<"\x{0000}"/utf8>>, <<""/utf8>>).
-file("src/ssevents/event.gleam", 47).
?DOC(
" Build a `Comment` from a text payload. CR (U+000D), LF (U+000A),\n"
" and NUL (U+0000) are stripped at construction so the result\n"
" round-trips through `encode → decode` without fanning out into\n"
" multiple comments. Matches the `sanitize_field_value` posture\n"
" already used for `event_name` and `id`.\n"
).
-spec comment(binary()) -> comment().
comment(Text) ->
{comment, sanitize_field_value(Text)}.
-file("src/ssevents/event.gleam", 58).
?DOC(
" Wrap a `Comment` as a stream item. Convenience for the common\n"
" `CommentItem(comment(text))` two-step.\n"
).
-spec comment_item(binary()) -> item().
comment_item(Text) ->
{comment_item, comment(Text)}.
-file("src/ssevents/event.gleam", 88).
-spec event(event(), binary()) -> event().
event(Event, Name) ->
{event,
{some, sanitize_field_value(Name)},
erlang:element(3, Event),
erlang:element(4, Event),
erlang:element(5, Event)}.
-file("src/ssevents/event.gleam", 97).
-spec id(event(), binary()) -> event().
id(Event, Id) ->
{event,
erlang:element(2, Event),
erlang:element(3, Event),
{some, sanitize_field_value(Id)},
erlang:element(5, Event)}.
-file("src/ssevents/event.gleam", 128).
-spec option_sanitize(gleam@option:option(binary())) -> gleam@option:option(binary()).
option_sanitize(Opt) ->
case Opt of
none ->
none;
{some, Value} ->
{some, sanitize_field_value(Value)}
end.
-file("src/ssevents/event.gleam", 155).
?DOC(
" Drop retry values that the SSE wire format / decoder will not\n"
" round-trip back to `Some(n)` under the default `Limits`.\n"
"\n"
" WHATWG SSE §9.2.6 only recognises retry values whose textual form\n"
" is ASCII digits, so a negative value would be silently dropped on\n"
" decode. Values above `limit.default_max_retry_value` (24 hours in\n"
" milliseconds) hard-fail the default decoder. Coercing both to\n"
" `None` here matches the silent-sanitisation posture\n"
" `sanitize_field_value` takes for `event:` and `id:`, so\n"
" `decode(encode(event))` returns the same event for any caller-built\n"
" `Event`. (#60)\n"
).
-spec sanitize_retry(gleam@option:option(integer())) -> gleam@option:option(integer()).
sanitize_retry(Retry) ->
case Retry of
none ->
none;
{some, Ms} ->
case (Ms < 0) orelse (Ms > 86400000) of
true ->
none;
false ->
{some, Ms}
end
end.
-file("src/ssevents/event.gleam", 135).
-spec retry(event(), integer()) -> event().
retry(Event, Milliseconds) ->
{event,
erlang:element(2, Event),
erlang:element(3, Event),
erlang:element(4, Event),
sanitize_retry({some, Milliseconds})}.
-file("src/ssevents/event.gleam", 198).
-spec map_data_grapheme(binary()) -> list(binary()).
map_data_grapheme(Grapheme) ->
case Grapheme of
<<"\r"/utf8>> ->
[];
<<"\x{0000}"/utf8>> ->
[];
<<"\r\n"/utf8>> ->
[<<"\n"/utf8>>];
Other ->
[Other]
end.
-file("src/ssevents/event.gleam", 191).
?DOC(
" Strip CR (U+000D) and NUL (U+0000) from a `data` value, and\n"
" convert standalone CRLF graphemes to LF.\n"
"\n"
" WHATWG SSE §9.2.6 normalises CR / CRLF / LF to LF on the wire side\n"
" and silently drops NUL, so neither sequence can survive\n"
" `decode(encode(x))` verbatim inside `data`. Strip / normalise both\n"
" at construction so the in-memory representation already matches\n"
" what the wire would carry. LF is preserved — `data` may\n"
" legitimately contain logical newlines, and the encoder splits on\n"
" LF to emit multi-line `data:` blocks; the decoder rejoins those\n"
" lines with LF, so `\\n` round-trips cleanly.\n"
"\n"
" The implementation maps over Unicode graphemes (`\\r\\n` is a single\n"
" grapheme per UAX #29). A `string.replace`-based pass cannot strip\n"
" the `\\r` half of a CRLF pair on the JavaScript target because the\n"
" CRLF grapheme is opaque to substring search.\n"
).
-spec sanitize_data_value(binary()) -> binary().
sanitize_data_value(Value) ->
_pipe = Value,
_pipe@1 = gleam@string:to_graphemes(_pipe),
_pipe@2 = gleam@list:flat_map(_pipe@1, fun map_data_grapheme/1),
gleam@string:join(_pipe@2, <<""/utf8>>).
-file("src/ssevents/event.gleam", 62).
-spec new(binary()) -> event().
new(Data) ->
{event, none, sanitize_data_value(Data), none, none}.
-file("src/ssevents/event.gleam", 66).
-spec from_parts(
gleam@option:option(binary()),
binary(),
gleam@option:option(binary()),
gleam@option:option(integer())
) -> event().
from_parts(Event_name, Data, Id, Retry) ->
{event,
option_sanitize(Event_name),
sanitize_data_value(Data),
option_sanitize(Id),
sanitize_retry(Retry)}.
-file("src/ssevents/event.gleam", 80).
-spec message(binary()) -> event().
message(Data) ->
new(Data).
-file("src/ssevents/event.gleam", 84).
-spec named(binary(), binary()) -> event().
named(Name, Data) ->
_pipe = new(Data),
event(_pipe, Name).
-file("src/ssevents/event.gleam", 166).
-spec data(event(), binary()) -> event().
data(Event, Data) ->
{event,
erlang:element(2, Event),
sanitize_data_value(Data),
erlang:element(4, Event),
erlang:element(5, Event)}.
-file("src/ssevents/event.gleam", 207).
-spec name_of(event()) -> gleam@option:option(binary()).
name_of(Event) ->
erlang:element(2, Event).
-file("src/ssevents/event.gleam", 211).
-spec data_of(event()) -> binary().
data_of(Event) ->
erlang:element(3, Event).
-file("src/ssevents/event.gleam", 215).
-spec id_of(event()) -> gleam@option:option(binary()).
id_of(Event) ->
erlang:element(4, Event).
-file("src/ssevents/event.gleam", 219).
-spec retry_of(event()) -> gleam@option:option(integer()).
retry_of(Event) ->
erlang:element(5, Event).
-file("src/ssevents/event.gleam", 223).
-spec event_item(event()) -> item().
event_item(Event) ->
{event_item, Event}.