Current section

Files

Jump to
caffeine_lang src caffeine_lang@codegen@relay.erl
Raw

src/caffeine_lang@codegen@relay.erl

-module(caffeine_lang@codegen@relay).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/caffeine_lang/codegen/relay.gleam").
-export([generate/1]).
-export_type([signal_entry/0, signal_kind/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.
-type signal_entry() :: {signal_entry,
binary(),
signal_kind(),
binary(),
gleam@dict:dict(binary(), caffeine_lang@value:value()),
gleam@dict:dict(binary(), binary()),
gleam@option:option(binary())}.
-type signal_kind() :: count | distribution.
-file("src/caffeine_lang/codegen/relay.gleam", 125).
?DOC(
" Serialize the `tags` dict — already `Dict(String, String)` because every\n"
" codegen-emitted tag is a string. Sorted by key for deterministic output.\n"
).
-spec tags_dict_json(gleam@dict:dict(binary(), binary())) -> gleam@json:json().
tags_dict_json(Tags) ->
_pipe = Tags,
_pipe@1 = maps:to_list(_pipe),
_pipe@2 = gleam@list:sort(
_pipe@1,
fun(A, B) ->
gleam@string:compare(erlang:element(1, A), erlang:element(1, B))
end
),
_pipe@3 = gleam@list:map(
_pipe@2,
fun(Pair) ->
{erlang:element(1, Pair),
gleam@json:string(erlang:element(2, Pair))}
end
),
gleam@json:object(_pipe@3).
-file("src/caffeine_lang/codegen/relay.gleam", 154).
-spec match_value_json(caffeine_lang@value:value()) -> gleam@json:json().
match_value_json(V) ->
case V of
{string_value, S} ->
gleam@json:string(S);
{int_value, I} ->
gleam@json:int(I);
{float_value, F} ->
gleam@json:float(F);
{bool_value, B} ->
gleam@json:bool(B);
_ ->
gleam@json:null()
end.
-file("src/caffeine_lang/codegen/relay.gleam", 146).
?DOC(
" Serialize a `match` dict. Match values are runtime-untyped at the relay,\n"
" so we emit each as its nearest JSON shape. Strings stay strings; numeric\n"
" and boolean values keep their JSON-native form. Other shapes fall back\n"
" to `null` (shouldn't appear post-validate). Entries are sorted by key for\n"
" deterministic output across compile targets — `dict.to_list` doesn't\n"
" guarantee insertion order on JavaScript.\n"
).
-spec match_dict_json(gleam@dict:dict(binary(), caffeine_lang@value:value())) -> gleam@json:json().
match_dict_json(Match) ->
_pipe = Match,
_pipe@1 = maps:to_list(_pipe),
_pipe@2 = gleam@list:sort(
_pipe@1,
fun(A, B) ->
gleam@string:compare(erlang:element(1, A), erlang:element(1, B))
end
),
_pipe@3 = gleam@list:map(
_pipe@2,
fun(Pair) ->
{erlang:element(1, Pair), match_value_json(erlang:element(2, Pair))}
end
),
gleam@json:object(_pipe@3).
-file("src/caffeine_lang/codegen/relay.gleam", 133).
-spec signal_kind_to_string(signal_kind()) -> binary().
signal_kind_to_string(Kind) ->
case Kind of
count ->
<<"count"/utf8>>;
distribution ->
<<"distribution"/utf8>>
end.
-file("src/caffeine_lang/codegen/relay.gleam", 109).
-spec signal_entry_json(signal_entry()) -> gleam@json:json().
signal_entry_json(Entry) ->
gleam@json:object(
[{<<"metric"/utf8>>, gleam@json:string(erlang:element(2, Entry))},
{<<"kind"/utf8>>,
gleam@json:string(
signal_kind_to_string(erlang:element(3, Entry))
)},
{<<"source"/utf8>>, gleam@json:string(erlang:element(4, Entry))},
{<<"match"/utf8>>, match_dict_json(erlang:element(5, Entry))},
{<<"tags"/utf8>>, tags_dict_json(erlang:element(6, Entry))},
{<<"value_path"/utf8>>, case erlang:element(7, Entry) of
none ->
gleam@json:null();
{some, P} ->
gleam@json:string(P)
end}]
).
-file("src/caffeine_lang/codegen/relay.gleam", 102).
?DOC(
" Wrap a list of entries in the top-level `signals.json` object: a version\n"
" tag plus the routing array. Schema version 2 adds the per-entry `tags`\n"
" field; relay builds before 2026-05-23 read v1 and don't know about tags.\n"
).
-spec signals_json(list(signal_entry())) -> gleam@json:json().
signals_json(Entries) ->
gleam@json:object(
[{<<"version"/utf8>>, gleam@json:int(2)},
{<<"signals"/utf8>>,
gleam@json:array(Entries, fun signal_entry_json/1)}]
).
-file("src/caffeine_lang/codegen/relay.gleam", 66).
?DOC(
" Build the list of signal entries from a single resolved IR by walking\n"
" `slo.indicators` for `ExternalSignal` variants. Literal-query indicators\n"
" are skipped — they don't need relay routing. Entries are sorted by\n"
" indicator name for deterministic output across compile targets.\n"
).
-spec ir_to_signal_entries(
caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:resolved())
) -> list(signal_entry()).
ir_to_signal_entries(Ir) ->
_pipe = erlang:element(3, erlang:element(5, Ir)),
_pipe@1 = maps:to_list(_pipe),
_pipe@2 = gleam@list:sort(
_pipe@1,
fun(A, B) ->
gleam@string:compare(erlang:element(1, A), erlang:element(1, B))
end
),
gleam@list:filter_map(
_pipe@2,
fun(Pair) ->
{Name, Src} = Pair,
case Src of
{external_signal, Source, Match, Value_extraction} ->
{ok,
{signal_entry,
<<"caffeine."/utf8,
(caffeine_lang@codegen@generator_utils:dd_metric_safe(
erlang:element(3, Ir)
))/binary>>,
case Value_extraction of
none ->
count;
{some, _} ->
distribution
end,
Source,
Match,
maps:from_list(
[{<<"indicator"/utf8>>,
caffeine_lang@codegen@generator_utils:dd_metric_safe(
Name
)}]
),
gleam@option:map(
Value_extraction,
fun(Ve) -> erlang:element(2, Ve) end
)}};
_ ->
{error, nil}
end
end
).
-file("src/caffeine_lang/codegen/relay.gleam", 52).
?DOC(false).
-spec generate(
list(caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:resolved()))
) -> gleam@option:option(binary()).
generate(Irs) ->
Entries = gleam@list:flat_map(Irs, fun ir_to_signal_entries/1),
case Entries of
[] ->
none;
_ ->
{some, gleam@json:to_string(signals_json(Entries))}
end.