-module(libero@codegen_stubs). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/libero/codegen_stubs.gleam"). -export([write_endpoint_client_stubs/3, write_config/1, write_ssr_flags/1]). -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( " Per-client RPC stub, config, and SSR-flag generators.\n" "\n" " Emits the files that ship inside each consumer client package:\n" " `messages.gleam` (typed RPC stubs), `rpc_config.gleam` + `_ffi.mjs`\n" " (WebSocket URL resolution), and `ssr.gleam` + `ssr_ffi.mjs` (SSR flag\n" " reader).\n" ). -file("src/libero/codegen_stubs.gleam", 20). ?DOC( " Generate typed client stubs from handler endpoints.\n" " Each endpoint becomes a function that sends a ClientMsg variant\n" " and decodes the response to RpcData(payload, domain).\n" ). -spec write_endpoint_client_stubs( list(libero@scanner:handler_endpoint()), binary(), binary() ) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_endpoint_client_stubs(Endpoints, Client_generated, Wire_module_tag) -> Shared_type_imports = libero@codegen:collect_endpoint_type_imports( Endpoints, true ), Client_msg_variants = libero@codegen:emit_client_msg_variants(Endpoints), Endpoint_blocks = gleam@list:map( Endpoints, fun(E) -> Fn_name = erlang:element(3, E), Variant_name = libero@codegen:to_pascal_case(erlang:element(3, E)), Payload_type = libero@field_type:to_gleam_source( erlang:element(4, E) ), Error_type = libero@field_type:to_gleam_source(erlang:element(5, E)), Fn_params = case erlang:element(6, E) of [] -> <<<<<<<<" on_response on_response: fn(RpcData("/utf8, Payload_type/binary>>/binary, ", "/utf8>>/binary, Error_type/binary>>/binary, ")) -> msg,"/utf8>>; Params -> Param_lines = gleam@list:map( Params, fun(P) -> <<<<<<<<<<<<" "/utf8, (erlang:element(1, P))/binary>>/binary, " "/utf8>>/binary, (erlang:element(1, P))/binary>>/binary, ": "/utf8>>/binary, (libero@field_type:to_gleam_source( erlang:element(2, P) ))/binary>>/binary, ","/utf8>> end ), <<<<<<<<<<(gleam@string:join(Param_lines, <<"\n"/utf8>>))/binary, "\n on_response on_response: fn(RpcData("/utf8>>/binary, Payload_type/binary>>/binary, ", "/utf8>>/binary, Error_type/binary>>/binary, ")) -> msg,"/utf8>> end, Msg_construct = libero@codegen:variant_pattern( Variant_name, erlang:element(6, E) ), <<<<<<<<<<<<<<<<<<<<<<<<<<<<"@external(javascript, \"./rpc_decoders_ffi.mjs\", \"decode_response_"/utf8, Fn_name/binary>>/binary, "\")\n"/utf8>>/binary, "fn decode_response_"/utf8>>/binary, Fn_name/binary>>/binary, "(raw: Dynamic) -> Dynamic\n\n"/utf8>>/binary, "pub fn "/utf8>>/binary, Fn_name/binary>>/binary, "(\n"/utf8>>/binary, Fn_params/binary>>/binary, "\n) -> Effect(msg) {\n send("/utf8>>/binary, Msg_construct/binary>>/binary, ", fn(raw) { on_response(wire.coerce(decode_response_"/utf8>>/binary, Fn_name/binary>>/binary, "(raw))) })\n}"/utf8>> end ), Option_import = libero@codegen:import_if( Endpoints, fun libero@codegen:is_option/1, <<"import gleam/option.{type Option}"/utf8>> ), Dict_import = libero@codegen:import_if( Endpoints, fun libero@codegen:is_dict/1, <<"import gleam/dict.{type Dict}"/utf8>> ), Content = <<<<<<<<<<<<<<<<<<<<<<"//// Code generated by libero. DO NOT EDIT. import generated/rpc_config import generated/rpc_decoders import gleam/dynamic.{type Dynamic} import libero/remote_data.{type RpcData} import libero/rpc import libero/wire import lustre/effect.{type Effect}"/utf8, Option_import/binary>>/binary, Dict_import/binary>>/binary, " "/utf8>>/binary, (gleam@string:join( Shared_type_imports, <<"\n"/utf8>> ))/binary>>/binary, " pub type ClientMsg { "/utf8>>/binary, (gleam@string:join( Client_msg_variants, <<"\n"/utf8>> ))/binary>>/binary, " } "/utf8>>/binary, (gleam@string:join(Endpoint_blocks, <<"\n\n"/utf8>>))/binary>>/binary, " fn send(msg: ClientMsg, on_response: fn(Dynamic) -> msg) -> Effect(msg) { let _ = rpc_decoders.ensure_decoders rpc.send( url: rpc_config.ws_url(), module: \""/utf8>>/binary, Wire_module_tag/binary>>/binary, "\", msg: msg, on_response: on_response, ) } "/utf8>>, Output = <>, libero@codegen:ensure_parent_dir(Output), libero@codegen:write_file(Output, Content). -file("src/libero/codegen_stubs.gleam", 161). ?DOC( " Write the rpc_config_ffi.mjs resolver invoked by `resolve_ws_url` in\n" " the generated Gleam config module.\n" ). -spec write_config_ffi(libero@config:config()) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_config_ffi(Config) -> Ffi_path = gleam@string:replace( erlang:element(5, 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. export function resolveWsUrl(path) { const protocol = globalThis.location?.protocol === \"https:\" ? \"wss:\" : \"ws:\"; const host = globalThis.location?.host ?? \"localhost\"; return protocol + \"//\" + host + path; } "/utf8>>, libero@codegen:write_file(Ffi_path, Ffi_content). -file("src/libero/codegen_stubs.gleam", 137). ?DOC( " Generate the client-side `rpc_config.gleam` plus its `_ffi.mjs`\n" " resolver.\n" ). -spec write_config(libero@config:config()) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_config(Config) -> Content = <<<<"//// Code generated by libero. DO NOT EDIT. //// //// WebSocket endpoint resolved at runtime from the browser's location. //// 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, (erlang:element(2, Config))/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>>, Output = erlang:element(5, Config), libero@codegen:ensure_parent_dir(Output), gleam@result:'try'( libero@codegen:write_file(Output, Content), fun(_) -> write_config_ffi(Config) end ). -file("src/libero/codegen_stubs.gleam", 181). ?DOC( " Generate the client-side SSR flags reader (gleam + mjs).\n" " Produces a read_flags() function that reads window.__LIBERO_FLAGS__\n" " and returns it as a Dynamic value for ssr.decode_flags().\n" ). -spec write_ssr_flags(binary()) -> {ok, nil} | {error, libero@gen_error:gen_error()}. write_ssr_flags(Client_generated) -> Gleam_path = <>, Ffi_path = <>, Gleam_content = <<"//// Code generated by libero. DO NOT EDIT. //// //// SSR flags reader for hydration. Reads the base64-encoded ETF //// flags embedded by the server in window.__LIBERO_FLAGS__. import gleam/dynamic.{type Dynamic} /// Read the SSR flags embedded in the page by the server. /// Returns a Dynamic value suitable for passing to ssr.decode_flags(). @external(javascript, \"./ssr_ffi.mjs\", \"readFlags\") pub fn read_flags() -> Dynamic { // Unreachable on the Erlang target - read_flags is client-only. panic as \"ssr.read_flags requires a browser environment\" } "/utf8>>, Ffi_content = <<"// Code generated by libero. DO NOT EDIT. // // Reads SSR flags embedded by the server for client hydration. export function readFlags() { return globalThis.__LIBERO_FLAGS__ ?? \"\"; } "/utf8>>, libero@codegen:ensure_parent_dir(Gleam_path), gleam@result:'try'( libero@codegen:write_file(Gleam_path, Gleam_content), fun(_) -> libero@codegen:write_file(Ffi_path, Ffi_content) end ).