//// A sans-IO effect kernel for the OAuth orchestration. An `Effect(a)` is a //// pure description of the HTTP + DPoP steps a flow needs; a per-target runner //// interprets it (sync `xrpc.Client` + `gose.Key` on erlang, async `Promise` //// `Client` + WebCrypto `CryptoKey` in the browser). Keys never appear here: //// `DpopProof` asks the runner for a proof and each runner closes over its own //// platform key. Bodies come in two shapes: `Fetch` for the string-shaped OAuth //// endpoints, `FetchBits` for the binary resource path (blobs). import atproto/xrpc.{type TransportError} import gleam/http/request.{type Request} import gleam/http/response.{type Response} import gleam/option.{type Option} pub type Effect(a) { Fetch( request: Request(String), next: fn(Result(Response(String), TransportError)) -> Effect(a), ) FetchBits( request: Request(BitArray), next: fn(Result(Response(BitArray), TransportError)) -> Effect(a), ) DpopProof( method: String, url: String, nonce: Option(String), ath: Option(String), next: fn(Result(String, String)) -> Effect(a), ) Done(a) } pub fn done(value: a) -> Effect(a) { Done(value) } /// Monadic bind, `use`-syntax friendly: `use x <- effect.then(some_effect)`. pub fn then(effect: Effect(a), f: fn(a) -> Effect(b)) -> Effect(b) { case effect { Done(value) -> f(value) Fetch(request, next) -> Fetch(request, fn(r) { then(next(r), f) }) FetchBits(request, next) -> FetchBits(request, fn(r) { then(next(r), f) }) DpopProof(method, url, nonce, ath, next) -> DpopProof(method, url, nonce, ath, fn(r) { then(next(r), f) }) } } pub fn map(effect: Effect(a), f: fn(a) -> b) -> Effect(b) { then(effect, fn(a) { Done(f(a)) }) } /// A string-bodied HTTP request, yielding the response (or a transport error). pub fn fetch( request: Request(String), ) -> Effect(Result(Response(String), TransportError)) { Fetch(request, Done) } /// A binary HTTP request (the resource path, for blobs), yielding the response. pub fn fetch_bits( request: Request(BitArray), ) -> Effect(Result(Response(BitArray), TransportError)) { FetchBits(request, Done) } /// Ask the runner for a DPoP proof bound to this method+URL (and optional nonce /// and access-token hash). The runner signs it with its platform key. pub fn proof( method method: String, url url: String, nonce nonce: Option(String), ath ath: Option(String), ) -> Effect(Result(String, String)) { DpopProof(method:, url:, nonce:, ath:, next: Done) }