-module(libero@remote_data). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/libero/remote_data.gleam"). -export([map/2, map2/3, 'try'/2, map_error/2, unwrap/2, to_option/1, fold/5, format_transport_error/1, format_failure/2]). -export_type([remote_data/2, rpc_outcome/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( " Typed states for async data loading.\n" " Inspired by Elm's RemoteData package.\n" "\n" " Use instead of Bool flags + Option fields for data that loads\n" " asynchronously. The view pattern matches directly on the state -\n" " impossible to show stale data while loading or forget to handle errors.\n" "\n" " Generated RPC stubs build `RpcData` directly: the per-endpoint\n" " `decode_response_` FFI returns an `RpcData(payload, domain)` that\n" " the page stores in its model. `NotAsked` and `Loading` are page-lifecycle\n" " states the page sets itself in `init` and `update`. `Failure` carries\n" " either a `TransportError(RpcError)` (framework / wire-level) or a typed\n" " `DomainError(domain)` from the handler's own error type.\n" ). -type remote_data(NEP, NEQ) :: not_asked | loading | {failure, NEQ} | {success, NEP}. -type rpc_outcome(NER) :: {transport_error, libero@error:rpc_error()} | {domain_error, NER}. -file("src/libero/remote_data.gleam", 43). ?DOC(" Apply a function to the success value.\n"). -spec map(remote_data(NEX, NEY), fun((NEX) -> NFB)) -> remote_data(NFB, NEY). map(Data, Transform) -> case Data of not_asked -> not_asked; loading -> loading; {failure, Error} -> {failure, Error}; {success, Value} -> {success, Transform(Value)} end. -file("src/libero/remote_data.gleam", 69). ?DOC( " Combine two independent `RemoteData` values with a function. Returns\n" " `Success` only when both inputs are `Success`. Short-circuits on the\n" " first `Failure` found (in either position), or `Loading` if either is\n" " `Loading` and neither has failed.\n" " When both inputs are `Failure`, the left-hand error is returned.\n" "\n" " Useful in `init` to batch parallel RPC calls into a single state\n" " transition:\n" "\n" " ```\n" " let result = remote_data.map2(config_rd, items_rd, fn(c, i) {\n" " #(c, i)\n" " })\n" " ```\n" ). -spec map2(remote_data(NFE, NFF), remote_data(NFI, NFF), fun((NFE, NFI) -> NFL)) -> remote_data(NFL, NFF). map2(A, B, Combine) -> case {A, B} of {{success, A@1}, {success, B@1}} -> {success, Combine(A@1, B@1)}; {{failure, E}, _} -> {failure, E}; {_, {failure, E@1}} -> {failure, E@1}; {loading, _} -> loading; {_, loading} -> loading; {_, _} -> not_asked end. -file("src/libero/remote_data.gleam", 91). ?DOC( " Monadic bind for chaining dependent async work. When the data is\n" " `Success`, apply `f` to the value and return its result. Otherwise\n" " propagate the current state unchanged.\n" "\n" " RPC calls that depend on a prior result usually live in separate\n" " `update` arms rather than inside `init`, so this is less common than\n" " `map` or `map2` — but available when callers need it.\n" ). -spec 'try'(remote_data(NFO, NFP), fun((NFO) -> remote_data(NFS, NFP))) -> remote_data(NFS, NFP). 'try'(Data, F) -> case Data of {success, Value} -> F(Value); not_asked -> not_asked; loading -> loading; {failure, Error} -> {failure, Error} end. -file("src/libero/remote_data.gleam", 104). ?DOC(" Apply a function to the error value.\n"). -spec map_error(remote_data(NFX, NFY), fun((NFY) -> NGB)) -> remote_data(NFX, NGB). map_error(Data, Transform) -> case Data of not_asked -> not_asked; loading -> loading; {failure, Error} -> {failure, Transform(Error)}; {success, Value} -> {success, Value} end. -file("src/libero/remote_data.gleam", 117). ?DOC(" Extract the success value, or return a default.\n"). -spec unwrap(remote_data(NGE, any()), NGE) -> NGE. unwrap(Data, Default) -> case Data of {success, Value} -> Value; _ -> Default end. -file("src/libero/remote_data.gleam", 125). ?DOC(" Convert to Option - Some for Success, None for everything else.\n"). -spec to_option(remote_data(NGI, any())) -> gleam@option:option(NGI). to_option(Data) -> case Data of {success, Value} -> {some, Value}; _ -> none end. -file("src/libero/remote_data.gleam", 135). ?DOC( " Reduce all four states into a single value. Each case provides its\n" " own callback so the caller can return whatever shape they need\n" " (e.g. an `Element(msg)` for a Lustre view).\n" ). -spec fold( remote_data(NGN, NGO), fun(() -> NGR), fun(() -> NGR), fun((NGO) -> NGR), fun((NGN) -> NGR) ) -> NGR. fold(Data, On_not_asked, On_loading, On_failure, On_success) -> case Data of not_asked -> On_not_asked(); loading -> On_loading(); {failure, Error} -> On_failure(Error); {success, Value} -> On_success(Value) end. -file("src/libero/remote_data.gleam", 153). ?DOC( " Default formatter for framework-level transport errors. Useful when\n" " the caller wants a single string for `RpcError` values rather than\n" " pattern-matching each variant.\n" ). -spec format_transport_error(libero@error:rpc_error()) -> binary(). format_transport_error(Err) -> case Err of {internal_error, _, Message} -> Message; {unknown_function, Name} -> <<"Unknown RPC: "/utf8, Name/binary>>; malformed_request -> <<"Malformed request"/utf8>> end. -file("src/libero/remote_data.gleam", 185). ?DOC( " Render an `RpcOutcome` as a single user-facing string. The caller\n" " supplies a formatter for their domain error type; transport errors\n" " are routed through `format_transport_error`.\n" "\n" " Use this anywhere two arms differ only by which formatter runs. In\n" " views:\n" "\n" " ```\n" " Failure(outcome) -> render(format_failure(outcome, format_my_error))\n" " ```\n" "\n" " In an update reducer that surfaces a flash message:\n" "\n" " ```\n" " LoadResult(Failure(outcome)) -> #(\n" " model,\n" " effect.none(),\n" " Some(#(Danger, format_failure(outcome, format_my_error))),\n" " )\n" " ```\n" "\n" " The match becomes exhaustive over `RpcOutcome`, so no\n" " `Failure(_)` catch-all is needed and the per-tier `DomainError(err)` /\n" " `TransportError(rpc_err)` arms collapse into one.\n" ). -spec format_failure(rpc_outcome(NGS), fun((NGS) -> binary())) -> binary(). format_failure(Outcome, Format_domain) -> case Outcome of {transport_error, Err} -> format_transport_error(Err); {domain_error, Err@1} -> Format_domain(Err@1) end.