-module(libero@codegen). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/libero/codegen.gleam"). -export([extract_dir/1, write_dispatch/3, write_send_functions/2, write_push_wrappers/2, write_websocket/1, write_config/1, write_atoms/2, module_to_mjs_path/1, write_register/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("src/libero/codegen.gleam", 176). ?DOC( " Convert a module path to a safe Gleam import alias.\n" " e.g. \"server/store\" -> \"server_store_handler\"\n" ). -spec handler_alias(binary()) -> binary(). handler_alias(Module_path) -> <<(gleam@string:replace(Module_path, <<"/"/utf8>>, <<"_"/utf8>>))/binary, "_handler"/utf8>>. -file("src/libero/codegen.gleam", 182). ?DOC( " Convert a message module path to a safe Gleam import alias.\n" " e.g. \"shared/messages/admin\" -> \"messages_admin\"\n" ). -spec message_alias(binary()) -> binary(). message_alias(Module_path) -> gleam@string:replace(Module_path, <<"/"/utf8>>, <<"_"/utf8>>). -file("src/libero/codegen.gleam", 755). ?DOC(" Write content to a file, logging the path on success.\n"). -spec write_file(binary(), binary()) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_file(Path, Content) -> case simplifile:write(Path, Content) of {ok, _} -> gleam_stdlib:println(<<" wrote "/utf8, Path/binary>>), {ok, nil}; {error, Cause} -> {error, {cannot_write_file, Path, Cause}} end. -file("src/libero/codegen.gleam", 670). ?DOC(" Write the rpc_config_ffi.mjs resolver file when --ws-path is active.\n"). -spec write_config_ffi(libero@config:config()) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_config_ffi(Config) -> case erlang:element(2, Config) of {ws_full_url, _} -> {ok, nil}; {ws_path_only, _} -> Ffi_path = gleam@string:replace( erlang:element(7, Config), <<".gleam"/utf8>>, <<"_ffi.mjs"/utf8>> ), Ffi_content = <<"// Code generated by libero. DO NOT EDIT. // // Resolves a WebSocket URL from the browser's current location + path. // Used by the generated rpc_config.gleam when --ws-path is active. export function resolveWsUrl(path) { const protocol = globalThis.location?.protocol === \"https:\" ? \"wss:\" : \"ws:\"; const host = globalThis.location?.host ?? \"localhost\"; return protocol + \"//\" + host + path; } "/utf8>>, write_file(Ffi_path, Ffi_content) end. -file("src/libero/codegen.gleam", 770). ?DOC( " Derive a Gleam module path from a generated file path.\n" " Uses /src/ split when available, falls back to stripping .gleam extension.\n" ). -spec generated_module_path(binary()) -> binary(). generated_module_path(Path) -> case gleam@string:split_once(Path, <<"/src/"/utf8>>) of {ok, {_, After_src}} -> case gleam_stdlib:string_ends_with(After_src, <<".gleam"/utf8>>) of true -> gleam@string:slice( After_src, 0, string:length(After_src) - 6 ); false -> After_src end; {error, nil} -> case gleam_stdlib:string_ends_with(Path, <<".gleam"/utf8>>) of true -> gleam@string:slice(Path, 0, string:length(Path) - 6); false -> Path end end. -file("src/libero/codegen.gleam", 799). -spec extract_dir(binary()) -> binary(). extract_dir(Path) -> case begin _pipe = gleam@string:split(Path, <<"/"/utf8>>), lists:reverse(_pipe) end of [_ | Rest_rev] -> case Rest_rev of [] -> <<"."/utf8>>; _ -> gleam@string:join(lists:reverse(Rest_rev), <<"/"/utf8>>) end; [] -> <<"."/utf8>> end. -file("src/libero/codegen.gleam", 794). ?DOC( " Create the parent directory for the given file path, ignoring any\n" " error. create_directory_all is idempotent (no error if the dir\n" " already exists) and any real write failure surfaces on the\n" " subsequent simplifile.write call.\n" ). -spec ensure_parent_dir(binary()) -> nil. ensure_parent_dir(Path) -> _ = simplifile:create_directory_all(extract_dir(Path)), nil. -file("src/libero/codegen.gleam", 19). ?DOC( " Generate the server dispatch module at `server_generated/dispatch.gleam`.\n" " The dispatch module decodes incoming wire calls and routes them by module\n" " name to the appropriate handler.\n" ). -spec write_dispatch(list(libero@scanner:message_module()), binary(), binary()) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_dispatch(Message_modules, Server_generated, Atoms_module) -> Msg_from_client_modules = gleam@list:filter( Message_modules, fun(M) -> erlang:element(4, M) end ), Handler_imports = gleam@list:flat_map( Msg_from_client_modules, fun(M@1) -> gleam@list:map( erlang:element(6, M@1), fun(Handler_mod) -> Alias = handler_alias(Handler_mod), <<<<<<"import "/utf8, Handler_mod/binary>>/binary, " as "/utf8>>/binary, Alias/binary>> end ) end ), Message_type_imports = gleam@list:filter_map( Msg_from_client_modules, fun(M@2) -> case erlang:element(6, M@2) of [_, _ | _] -> Alias@1 = message_alias(erlang:element(2, M@2)), {ok, <<<<<<"import "/utf8, (erlang:element(2, M@2))/binary>>/binary, " as "/utf8>>/binary, Alias@1/binary>>}; _ -> {error, nil} end end ), Case_arms = gleam@list:filter_map( Msg_from_client_modules, fun(M@3) -> case erlang:element(6, M@3) of [] -> {error, nil}; [Single_handler] -> Alias@2 = handler_alias(Single_handler), {ok, <<<<<<<<" Ok(#(\""/utf8, (erlang:element(2, M@3))/binary>>/binary, "\", msg)) ->\n dispatch(state, fn() { "/utf8>>/binary, Alias@2/binary>>/binary, ".update_from_client(msg: wire.coerce(msg), state:) })"/utf8>>}; [First_handler | Rest_handlers] -> Msg_alias = message_alias(erlang:element(2, M@3)), First_alias = handler_alias(First_handler), Typed_msg_line = <<<<" let typed_msg: "/utf8, Msg_alias/binary>>/binary, ".MsgFromClient = wire.coerce(msg)"/utf8>>, First_call = <<<<" let result = "/utf8, First_alias/binary>>/binary, ".update_from_client(msg: typed_msg, state:)"/utf8>>, Chain_calls = gleam@list:map( Rest_handlers, fun(Handler_mod@1) -> Alias@3 = handler_alias(Handler_mod@1), <<<<" let result = case result {\n Error(UnhandledMessage) -> "/utf8, Alias@3/binary>>/binary, ".update_from_client(msg: typed_msg, state:)\n other -> other\n }"/utf8>> end ), Body = gleam@string:join( [Typed_msg_line, First_call | Chain_calls], <<"\n"/utf8>> ), {ok, <<<<<<<<<<" Ok(#(\""/utf8, (erlang:element(2, M@3))/binary>>/binary, "\", msg)) -> {\n"/utf8>>/binary, Body/binary>>/binary, "\n dispatch(state, fn() { result })\n"/utf8>>/binary, " }"/utf8>>} end end ), Ok_unknown_arm = <<" Ok(#(name, _)) ->\n #(wire.tag_response(wire.encode(Error(UnknownFunction(name)))), None, state)"/utf8>>, Error_arm = <<" Error(_) ->\n #(wire.tag_response(wire.encode(Error(MalformedRequest))), None, state)"/utf8>>, All_arms = lists:append([Case_arms, [Ok_unknown_arm, Error_arm]]), Needs_unhandled_import = gleam@list:any( Msg_from_client_modules, fun(M@4) -> case erlang:element(6, M@4) of [_, _ | _] -> true; _ -> false end end ), All_imports = lists:append([Message_type_imports, Handler_imports]), Content = <<<<<<<<<<<<<<<<"//// Code generated by libero. DO NOT EDIT. import gleam/option.{type Option, None, Some} import libero/error.{type PanicInfo, InternalError, MalformedRequest, UnknownFunction} import libero/trace import libero/wire import server/app_error.{type AppError"/utf8, (case Needs_unhandled_import of true -> <<", UnhandledMessage"/utf8>>; false -> <<""/utf8>> end)/binary>>/binary, "} import server/shared_state.{type SharedState} "/utf8>>/binary, (gleam@string:join(All_imports, <<"\n"/utf8>>))/binary>>/binary, " @external(erlang, \""/utf8>>/binary, Atoms_module/binary>>/binary, "\", \"ensure\") fn ensure_atoms() -> Nil pub fn handle( state state: SharedState, data data: BitArray, ) -> #(BitArray, Option(PanicInfo), SharedState) { let Nil = ensure_atoms() case wire.decode_call(data) { "/utf8>>/binary, (gleam@string:join(All_arms, <<"\n"/utf8>>))/binary>>/binary, " } } fn dispatch( state state: SharedState, call call: fn() -> Result(#(a, SharedState), AppError), ) -> #(BitArray, Option(PanicInfo), SharedState) { case trace.try_call(call) { Ok(Ok(#(value, new_state))) -> // Strip the MsgFromServer envelope - the FIFO response/request // pairing on the client makes the variant tag redundant. The wire // ships the payload directly as Result(payload, RpcError). #(wire.tag_response(wire.encode(Ok(wire.unwrap_response(value)))), None, new_state) Ok(Error(app_err)) -> #(wire.tag_response(wire.encode(Error(error.AppError(app_err)))), None, state) Error(reason) -> { let trace_id = trace.new_trace_id() #( wire.tag_response(wire.encode(Error(InternalError(trace_id, \"Internal server error\")))), Some(error.PanicInfo(trace_id:, fn_name: \"dispatch\", reason:)), state, ) } } } "/utf8>>, Output = <>, ensure_parent_dir(Output), write_file(Output, Content). -file("src/libero/codegen.gleam", 191). ?DOC( " Generate per-module send function files under `client_generated/`.\n" " For each message module with `has_msg_from_client == True`, generates a\n" " `.gleam` file with a `send_to_server` function that wraps `rpc.send`.\n" ). -spec write_send_functions(list(libero@scanner:message_module()), binary()) -> {ok, nil} | {error, list(libero@gen_error:gen_error())}. write_send_functions(Message_modules, Client_generated) -> Msg_from_client_modules = gleam@list:filter( Message_modules, fun(M) -> erlang:element(4, M) end ), Errors = gleam@list:fold( Msg_from_client_modules, [], fun(Errs, M@1) -> Segment = libero@scanner:last_module_segment(erlang:element(2, M@1)), Content = <<<<<<<<<<<<<<<<<<<<"//// Code generated by libero. DO NOT EDIT. import gleam/dynamic.{type Dynamic} import "/utf8, (erlang:element(2, M@1))/binary>>/binary, ".{type MsgFromClient} import libero/rpc import "/utf8>>/binary, (generated_module_path( <> ))/binary>>/binary, " import "/utf8>>/binary, (generated_module_path( <> ))/binary>>/binary, " import lustre/effect.{type Effect} pub fn send_to_server( msg msg: MsgFromClient, on_response on_response: fn(Dynamic) -> msg, ) -> Effect(msg) { rpc_register.register_all() rpc.send( url: rpc_config.ws_url(), module: \""/utf8>>/binary, (erlang:element(2, M@1))/binary>>/binary, "\", msg: msg, on_response: on_response, ) } pub fn update_from_server( handler handler: fn(Dynamic) -> msg, ) -> Effect(msg) { rpc_register.register_all() rpc.update_from_server( module: \""/utf8>>/binary, (erlang:element(2, M@1))/binary>>/binary, "\", handler: handler, ) } "/utf8>>, Output = <<<<<>/binary, Segment/binary>>/binary, ".gleam"/utf8>>, ensure_parent_dir(Output), case simplifile:write(Output, Content) of {ok, _} -> gleam_stdlib:println(<<" wrote "/utf8, Output/binary>>), Errs; {error, Cause} -> [{cannot_write_file, Output, Cause} | Errs] end end ), case Errors of [] -> {ok, nil}; _ -> {error, lists:reverse(Errors)} end. -file("src/libero/codegen.gleam", 256). ?DOC( " Generate per-module push wrapper files under `server_generated/`.\n" " For each message module with `has_msg_from_server == True`, generates a\n" " `.gleam` file with `send_to_client` and `send_to_clients`\n" " functions that bake in the module string.\n" ). -spec write_push_wrappers(list(libero@scanner:message_module()), binary()) -> {ok, nil} | {error, list(libero@gen_error:gen_error())}. write_push_wrappers(Message_modules, Server_generated) -> Msg_from_server_modules = gleam@list:filter( Message_modules, fun(M) -> erlang:element(5, M) end ), Errors = gleam@list:fold( Msg_from_server_modules, [], fun(Errs, M@1) -> Segment = libero@scanner:last_module_segment(erlang:element(2, M@1)), Content = <<<<<<<<<<<<"//// Code generated by libero. DO NOT EDIT. import libero/push import "/utf8, (erlang:element(2, M@1))/binary>>/binary, ".{type MsgFromServer} pub fn send_to_client( client_id client_id: String, msg msg: MsgFromServer, ) -> Nil { push.send_to_client(client_id:, module: \""/utf8>>/binary, (erlang:element(2, M@1))/binary>>/binary, "\", msg:) } pub fn send_to_clients( topic topic: String, msg msg: MsgFromServer, ) -> Nil { push.send_to_clients(topic:, module: \""/utf8>>/binary, (erlang:element(2, M@1))/binary>>/binary, "\", msg:) } "/utf8>>, Output = <<<<<>/binary, Segment/binary>>/binary, ".gleam"/utf8>>, ensure_parent_dir(Output), case simplifile:write(Output, Content) of {ok, _} -> gleam_stdlib:println(<<" wrote "/utf8, Output/binary>>), Errs; {error, Cause} -> [{cannot_write_file, Output, Cause} | Errs] end end ), case Errors of [] -> {ok, nil}; _ -> {error, lists:reverse(Errors)} end. -file("src/libero/codegen.gleam", 306). ?DOC( " Generate the server websocket handler at `server_generated/websocket.gleam`.\n" " Also writes the Erlang FFI for decoding push messages.\n" ). -spec write_websocket(binary()) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_websocket(Server_generated) -> Gleam_output = <>, Module_path = case gleam@string:split_once( Server_generated, <<"src/"/utf8>> ) of {ok, {_, After}} -> After; {error, nil} -> Server_generated end, Ffi_module = <<(gleam@string:replace( Module_path, <<"/"/utf8>>, <<"@"/utf8>> ))/binary, "@websocket"/utf8>>, Ffi_output = <<<<"src/"/utf8, Ffi_module/binary>>/binary, "_ffi.erl"/utf8>>, Content = <<<<<<<<"//// Code generated by libero. DO NOT EDIT. //// //// WebSocket handler for mist. Handles dispatch, push frame //// forwarding, and topic cleanup on disconnect. import gleam/dynamic.{type Dynamic} import gleam/erlang/process import gleam/http/request.{type Request} import gleam/list import gleam/option.{type Option, Some} import gleam/string import libero/push import libero/ws_logger.{type Logger} import mist.{type Connection} import "/utf8, Module_path/binary>>/binary, "/dispatch import server/shared_state.{type SharedState} pub type ConnState { ConnState(state: SharedState, topics: List(String), logger: Logger) } type PushMsg { PushFrame(BitArray) Ignored } /// Wire up a WebSocket upgrade with dispatch, push, and topic management. /// The `logger` argument receives connect/disconnect, panic, and send /// failure messages. Use `ws_logger.default_logger()` for stdout output /// or pass your own structured logger. pub fn upgrade( request req: Request(Connection), state state: SharedState, topics topics: List(String), logger logger: Logger, ) { mist.websocket( request: req, handler: handler, on_init: on_init(state, topics, logger), on_close: fn(state) { list.each(state.topics, fn(t) { push.leave(topic: t) }) logger.debug(\"WebSocket: disconnected\") }, ) } fn on_init(state: SharedState, topics: List(String), logger: Logger) { fn(_conn: mist.WebsocketConnection) -> #(ConnState, Option(process.Selector(PushMsg))) { logger.debug(\"WebSocket: connected\") list.each(topics, fn(t) { push.join(topic: t) }) let selector = process.new_selector() |> process.select_other(fn(msg: Dynamic) { case decode_push_msg(msg) { Ok(frame) -> PushFrame(frame) Error(Nil) -> Ignored } }) #(ConnState(state:, topics:, logger:), Some(selector)) } } fn handler( state: ConnState, message: mist.WebsocketMessage(PushMsg), conn: mist.WebsocketConnection, ) { case message { mist.Binary(data) -> { let #(response_bytes, maybe_panic, new_state) = dispatch.handle(state: state.state, data:) case maybe_panic { Some(info) -> state.logger.error( \"RPC panic: \" <> info.fn_name <> \" (trace \" <> info.trace_id <> \"): \" <> info.reason, ) _ -> Nil } case mist.send_binary_frame(conn, response_bytes) { Ok(_) -> Nil Error(reason) -> state.logger.warning( \"Failed to send WebSocket frame: \" <> string.inspect(reason), ) } mist.continue(ConnState(..state, state: new_state)) } mist.Custom(PushFrame(frame)) -> { case mist.send_binary_frame(conn, frame) { Ok(_) -> Nil Error(reason) -> state.logger.warning( \"Failed to send WebSocket push frame: \" <> string.inspect(reason), ) } mist.continue(state) } mist.Custom(Ignored) -> mist.continue(state) mist.Closed | mist.Shutdown -> mist.stop() mist.Text(_) -> mist.continue(state) } } @external(erlang, \""/utf8>>/binary, Ffi_module/binary>>/binary, "_ffi\", \"decode_push_msg\") fn decode_push_msg(msg: Dynamic) -> Result(BitArray, Nil) "/utf8>>, Ffi_content = <<<<<<"-module('"/utf8, Ffi_module/binary>>/binary, "_ffi').\n"/utf8>>/binary, "-export([decode_push_msg/1]). decode_push_msg({libero_push, Frame}) when is_binary(Frame) -> {ok, Frame}; decode_push_msg(_) -> {error, nil}. "/utf8>>, ensure_parent_dir(Gleam_output), gleam@result:'try'( write_file(Gleam_output, Content), fun(_) -> ensure_parent_dir(Ffi_output), write_file(Ffi_output, Ffi_content) end ). -file("src/libero/codegen.gleam", 467). ?DOC( " Write the tiny Gleam wrapper for type registration.\n" " Called automatically by generated send functions; safe to call\n" " multiple times (idempotent on the JS side).\n" ). -spec write_register_gleam(libero@config:config()) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_register_gleam(Config) -> Content = <<"//// Code generated by libero. DO NOT EDIT. //// //// Registers every consumer custom type that might cross the wire //// so libero's client-side rebuild function can reconstruct tagged //// ETF terms into Gleam class instances. //// //// Called automatically by generated send functions. Safe to call //// manually if needed (idempotent). pub fn register_all() -> Nil { do_register_all() } @external(javascript, \"./rpc_register_ffi.mjs\", \"registerAll\") fn do_register_all() -> Nil "/utf8>>, Output = erlang:element(8, Config), ensure_parent_dir(Output), write_file(Output, Content). -file("src/libero/codegen.gleam", 634). -spec write_config(libero@config:config()) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_config(Config) -> Content = case erlang:element(2, Config) of {ws_full_url, Url} -> <<<<"//// Code generated by libero. DO NOT EDIT. //// //// WebSocket endpoint the client connects to at runtime. //// Set via --ws-url at generation time. Rerun the libero codegen //// to regenerate if the URL changes. pub fn ws_url() -> String { \""/utf8, Url/binary>>/binary, "\" } "/utf8>>; {ws_path_only, Path} -> <<<<"//// Code generated by libero. DO NOT EDIT. //// //// WebSocket endpoint resolved at runtime from the browser's location. //// Set via --ws-path at generation time. The scheme (ws/wss) and host //// are inferred from window.location so one compiled bundle works //// across all subdomains. pub fn ws_url() -> String { resolve_ws_url(\""/utf8, Path/binary>>/binary, "\") } @external(javascript, \"./rpc_config_ffi.mjs\", \"resolveWsUrl\") fn resolve_ws_url(_path: String) -> String { panic as \"resolve_ws_url requires a browser environment (window.location)\" } "/utf8>> end, Output = erlang:element(7, Config), ensure_parent_dir(Output), gleam@result:'try'( write_file(Output, Content), fun(_) -> write_config_ffi(Config) end ). -file("src/libero/codegen.gleam", 699). ?DOC( " Generate an Erlang FFI file that pre-registers all constructor atoms\n" " discovered by the type graph walker, plus framework atoms used by\n" " libero's wire protocol. Calling `ensure/0` from this module creates\n" " the atoms in the BEAM atom table so that `binary_to_term([safe])`\n" " can decode client ETF payloads without rejecting unknown atoms.\n" ). -spec write_atoms( libero@config:config(), list(libero@walker:discovered_variant()) ) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_atoms(Config, Discovered) -> Framework_atoms = [<<"ok"/utf8>>, <<"error"/utf8>>, <<"some"/utf8>>, <<"none"/utf8>>, <<"nil"/utf8>>, <<"true"/utf8>>, <<"false"/utf8>>, <<"app_error"/utf8>>, <<"malformed_request"/utf8>>, <<"unknown_function"/utf8>>, <<"internal_error"/utf8>>, <<"decode_error"/utf8>>], Discovered_atoms = gleam@list:map( Discovered, fun(V) -> erlang:element(4, V) end ), All_atoms = begin _pipe = lists:append(Framework_atoms, Discovered_atoms), _pipe@1 = gleam@list:unique(_pipe), gleam@list:sort(_pipe@1, fun gleam@string:compare/2) end, Atom_list = begin _pipe@2 = gleam@list:map( All_atoms, fun(Atom) -> <<<<" <<\""/utf8, Atom/binary>>/binary, "\">>"/utf8>> end ), gleam@string:join(_pipe@2, <<",\n"/utf8>>) end, Content = <<<<<<<<"%% Code generated by libero. DO NOT EDIT. %% %% Pre-registers all constructor atoms that may appear in client ETF %% payloads, so binary_to_term([safe]) can decode them without %% rejecting unknown atoms. %% %% ensure/0 uses persistent_term as a one-shot guard so the %% binary_to_atom calls only run once per VM lifetime. %% %% lists:foreach + fun is used instead of bare binary_to_atom calls %% because the Erlang compiler optimizes away pure BIF calls whose %% results are discarded. -module("/utf8, (erlang:element(6, Config))/binary>>/binary, "). -export([ensure/0]). ensure() -> case persistent_term:get({?MODULE, done}, false) of true -> nil; false -> do_ensure() end. do_ensure() -> lists:foreach(fun(B) -> binary_to_atom(B) end, [ "/utf8>>/binary, Atom_list/binary>>/binary, " ]), persistent_term:put({?MODULE, done}, true), nil. "/utf8>>, Output = erlang:element(5, Config), ensure_parent_dir(Output), write_file(Output, Content). -file("src/libero/codegen.gleam", 814). ?DOC( " Convert a Gleam module path like \"shared/discount\" to its compiled\n" " .mjs bundle path \"shared/shared/discount.mjs\". The first segment\n" " is the package name (Gleam convention) and is repeated because\n" " the bundle layout is `/.mjs`.\n" ). -spec module_to_mjs_path(binary()) -> binary(). module_to_mjs_path(Module_path) -> case gleam@string:split_once(Module_path, <<"/"/utf8>>) of {error, nil} -> <<<<<>/binary, Module_path/binary>>/binary, ".mjs"/utf8>>; {ok, {Package, _}} -> <<<<<>/binary, Module_path/binary>>/binary, ".mjs"/utf8>> end. -file("src/libero/codegen.gleam", 492). ?DOC( " Write the FFI .mjs file with explicit imports and registerConstructor\n" " calls for every variant discovered by the type graph walker.\n" ). -spec write_register_ffi( libero@config:config(), list(libero@walker:discovered_variant()) ) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_register_ffi(Config, Discovered) -> Prefix = erlang:element(10, Config), Distinct_modules = begin _pipe = gleam@list:fold( Discovered, {[], gleam@set:new()}, fun(Acc, V) -> {Modules_acc, Seen} = Acc, case gleam@set:contains(Seen, erlang:element(2, V)) of true -> Acc; false -> {lists:append(Modules_acc, [erlang:element(2, V)]), gleam@set:insert(Seen, erlang:element(2, V))} end end ), (fun(Pair) -> erlang:element(1, Pair) end)(_pipe) end, Module_aliases = gleam@list:index_fold( Distinct_modules, maps:new(), fun(Acc@1, Module_path, I) -> gleam@dict:insert( Acc@1, Module_path, <<"_m"/utf8, (erlang:integer_to_binary(I))/binary>> ) end ), Libero_import = <<<<"import { registerConstructor, registerFloatFields, setListCtors, setDictFromList, setGleamCustomType } from \""/utf8, Prefix/binary>>/binary, "libero/libero/rpc_ffi.mjs\";"/utf8>>, Framework_imports = [<<<<"import { Ok, Error, CustomType, Empty, NonEmpty } from \""/utf8, Prefix/binary>>/binary, "gleam_stdlib/gleam.mjs\";"/utf8>>, <<<<"import { DecodeError } from \""/utf8, Prefix/binary>>/binary, "libero/libero/wire.mjs\";"/utf8>>, <<<<"import { AppError, MalformedRequest, UnknownFunction, InternalError } from \""/utf8, Prefix/binary>>/binary, "libero/libero/error.mjs\";"/utf8>>, <<<<"import { Some, None } from \""/utf8, Prefix/binary>>/binary, "gleam_stdlib/gleam/option.mjs\";"/utf8>>, <<<<"import { from_list as dictFromList } from \""/utf8, Prefix/binary>>/binary, "gleam_stdlib/gleam/dict.mjs\";"/utf8>>], Module_imports = gleam@list:map( Distinct_modules, fun(Module_path@1) -> Alias = begin _pipe@1 = gleam_stdlib:map_get(Module_aliases, Module_path@1), gleam@result:unwrap(_pipe@1, <<"_m0"/utf8>>) end, <<<<<<<<<<"import * as "/utf8, Alias/binary>>/binary, " from \""/utf8>>/binary, Prefix/binary>>/binary, (module_to_mjs_path(Module_path@1))/binary>>/binary, "\";"/utf8>> end ), Register_calls = gleam@list:map( Discovered, fun(V@1) -> Alias@1 = begin _pipe@2 = gleam_stdlib:map_get( Module_aliases, erlang:element(2, V@1) ), gleam@result:unwrap(_pipe@2, <<"_m0"/utf8>>) end, <<<<<<<<<<<<<<<<<<<<" if ("/utf8, Alias@1/binary>>/binary, "."/utf8>>/binary, (erlang:element(3, V@1))/binary>>/binary, ") registerConstructor(\""/utf8>>/binary, (erlang:element(4, V@1))/binary>>/binary, "\", "/utf8>>/binary, Alias@1/binary>>/binary, "."/utf8>>/binary, (erlang:element(3, V@1))/binary>>/binary, ");"/utf8>> end ), Float_field_calls = gleam@list:filter_map( Discovered, fun(V@2) -> case erlang:element(5, V@2) of [] -> {error, nil}; Indices -> Indices_str = begin _pipe@3 = gleam@list:map( Indices, fun erlang:integer_to_binary/1 ), gleam@string:join(_pipe@3, <<", "/utf8>>) end, {ok, <<<<<<<<" registerFloatFields(\""/utf8, (erlang:element(4, V@2))/binary>>/binary, "\", ["/utf8>>/binary, Indices_str/binary>>/binary, "]);"/utf8>>} end end ), Framework_register_calls = [<<" // Framework types"/utf8>>, <<" setGleamCustomType(CustomType);"/utf8>>, <<" setListCtors(Empty, NonEmpty);"/utf8>>, <<" setDictFromList(dictFromList);"/utf8>>, <<" registerConstructor(\"ok\", Ok);"/utf8>>, <<" registerConstructor(\"error\", Error);"/utf8>>, <<" registerConstructor(\"some\", Some);"/utf8>>, <<" registerConstructor(\"none\", None);"/utf8>>, <<" registerConstructor(\"decode_error\", DecodeError);"/utf8>>, <<" registerConstructor(\"app_error\", AppError);"/utf8>>, <<" registerConstructor(\"malformed_request\", MalformedRequest);"/utf8>>, <<" registerConstructor(\"unknown_function\", UnknownFunction);"/utf8>>, <<" registerConstructor(\"internal_error\", InternalError);"/utf8>>], All_calls = lists:append( [Framework_register_calls, Register_calls, Float_field_calls] ), Content = <<<<<<<<<<<<<<<<"// Code generated by libero. DO NOT EDIT. // // Registers framework and application custom types for wire codec // reconstruction. Framework types (Ok, Error, Some, None, etc.) are // always included. Application types are discovered by walking the // MsgFromClient/MsgFromServer type graphs at generation time. // // Called automatically by generated send functions. Idempotent - // safe to call multiple times (only runs registration once). "/utf8, Libero_import/binary>>/binary, "\n"/utf8>>/binary, (gleam@string:join(Framework_imports, <<"\n"/utf8>>))/binary>>/binary, "\n"/utf8>>/binary, (gleam@string:join(Module_imports, <<"\n"/utf8>>))/binary>>/binary, " let registered = false; export function registerAll() { if (registered) return; try { registered = true; "/utf8>>/binary, (gleam@string:join(All_calls, <<"\n"/utf8>>))/binary>>/binary, " } catch (e) { registered = false; const msg = \"libero: type registration failed. \" + \"This usually means the generated code is stale or the project \" + \"has not been compiled yet. Re-run: gleam run -m libero -- ...\\n\" + \"Original error: \" + (e.message || e); throw new globalThis.Error(msg); } } "/utf8>>, Output = erlang:element(9, Config), ensure_parent_dir(Output), write_file(Output, Content). -file("src/libero/codegen.gleam", 451). ?DOC( " Write the client-side type registration files (gleam wrapper + mjs FFI).\n" " Uses the pre-discovered variant list instead of walking from function signatures.\n" ). -spec write_register( libero@config:config(), list(libero@walker:discovered_variant()) ) -> {ok, nil} | {error, list(libero@gen_error:gen_error())}. write_register(Config, Discovered) -> gleam@result:'try'( begin _pipe = write_register_gleam(Config), gleam@result:map_error(_pipe, fun(E) -> [E] end) end, fun(_) -> gleam@result:'try'( begin _pipe@1 = write_register_ffi(Config, Discovered), gleam@result:map_error(_pipe@1, fun(E@1) -> [E@1] end) end, fun(_) -> {ok, nil} end ) end ).