///|
/// Runs `main` on moonbitlang/core/async's event loop and returns when it
/// and every task it started have finished. An app calls it from a plain
/// `fn main`, so it needs neither an import of core/async nor its
/// experimental opt-in:
///
/// ```mbt nocheck
/// fn main {
/// @mttp_async.run(async fn() { ... })
/// }
/// ```
pub fn run(main : async () -> Unit) -> Unit {
@async.run_async_main(main)
}
///|
/// A Client over the real network with the defaults an API client wants:
///
/// - `retry` (default `RetryPolicy::conservative()`): up to two retries of an
/// idempotent request on 408, 409, 429, 500, 502, 503 or 504 or a network
/// failure, and of any request that was never sent, honouring
/// `Retry-After` (a server that asks to wait more than 60 s is not
/// retried); set it to `RetryPolicy::none()` to turn retries off.
/// - `retry_budget` (default `RetryBudget::new()`): retries stay within 20 %
/// of the last 10 s of requests plus 10 per second.
/// - `timeout_ms` (default 30 s) for each attempt, and `total_ms` (default
/// 120 s) for the whole call, retries and their waits included.
/// - `redirect` (default `RedirectConfig::default()`): up to 10 hops, no
/// HTTPS-to-HTTP downgrade, credentials dropped across origins;
/// `RedirectConfig::none()` turns redirects off, and a request opts out
/// with `RequestPlan::follow_redirects(false)`.
/// - Responses are decompressed (gzip, deflate, br, zstd) and buffered
/// bodies are limited to 64 MiB after decoding (`max_body_bytes`).
/// - `cookies` (no jar by default): a `CookieJar` that stores every
/// response's `Set-Cookie` and sends matching cookies, on each redirect
/// hop too. The jar belongs to the caller, so several Clients may share
/// it and it outlives them:
///
/// ```mbt nocheck
/// let jar = @mttp_async.CookieJar::new(@mttp_async.clock())
///
/// let site = @mttp_async.client(cookies=jar)
/// ```
///
/// `base_url` is joined to each relative request target, `headers` are sent
/// with every request, `limiter` admits requests (no limit by default),
/// `trust` chooses the TLS roots and `config` tunes the connection pool.
/// Close the Client when done; that closes its connections.
///
/// `backend`, `clock` and `timer` replace the real network, the system
/// clock and the event loop's timers, so a test gets the same Client, with
/// all the defaults above, over a stub such as `@testing.BackendStub` and a
/// virtual clock. `timer` defaults to a timer on `clock`, so a virtual clock
/// drives the deadlines too; without `backend`, the network backend runs its
/// connection deadlines and pool expiry on the same clock and timer. A given
/// `backend` is used as it is: `trust` and `config` configure only the
/// network backend this builds when `backend` is omitted, and are ignored
/// otherwise.
pub fn client(
base_url~ : String = "",
headers~ : @mttp.Headers = @mttp.Headers::empty(),
timeout_ms~ : Int32 = 30_000,
total_ms~ : Int32 = 120_000,
max_body_bytes? : Int32,
retry~ : @client.RetryPolicy = @client.RetryPolicy::conservative(),
retry_budget~ : @client.RetryBudget = @client.RetryBudget::new(),
redirect~ : @client.RedirectConfig = @client.RedirectConfig::default(),
limiter? : &@client.RateLimiter,
trust~ : @trust.Trust = System,
config? : @h1client.ClientConfig,
cookies? : @cookie.CookieJar,
backend? : &@mttp.Backend,
clock? : &@rt.Clock,
timer? : &@rt.Timer,
) -> @client.Client {
let clock = clock.unwrap_or_else(system_clock)
let timer = timer.unwrap_or_else(() => @runtime.ClockTimer::new(clock~))
@client.Client::new(
backend.unwrap_or_else(() => network(trust, config, clock, timer)),
clock,
base_url~,
headers~,
timeout_ms~,
max_body_bytes?,
timer~,
retry~,
retry_budget~,
redirect~,
cookies?,
limiter?,
total_ms~,
)
}
///|
/// The real network as a `Backend`: HTTP/1.1 over TCP and TLS with
/// connection reuse and decompressed responses. It is only the I/O: the
/// `Client` built over it follows redirects, and keeps cookies when given a
/// jar, as `Client::new` does over any backend. Pass it, with `clock` and
/// `timer`, to a library that builds its own `Client`, such as an API SDK:
///
/// ```mbt nocheck
/// let claude = @anthropic.Anthropic::new(
/// api_key~,
/// @mttp_async.backend(),
/// @mttp_async.clock(),
/// timer=@mttp_async.timer(),
/// )
/// ```
///
/// The libraries keep their own defaults (base URL, authentication, retries)
/// and follow redirects through `Client::new`; this only supplies the I/O.
/// Closing the library's Client closes the connections.
pub fn backend(
trust~ : @trust.Trust = System,
config? : @h1client.ClientConfig,
) -> &@mttp.Backend {
let clock = system_clock()
network(trust, config, clock, @runtime.ClockTimer::new(clock~))
}
///|
fn network(
trust : @trust.Trust,
config : @h1client.ClientConfig?,
clock : &@rt.Clock,
timer : &@rt.Timer,
) -> &@mttp.Backend {
@backend.AsyncBackend::new(config?, trust~, clock~, timer~)
}
///|
fn system_clock() -> &@rt.Clock {
@runtime.AsyncClock::new()
}
///|
/// The system's wall and monotonic clock, for a `Client` built by hand.
pub fn clock() -> &@rt.Clock {
system_clock()
}
///|
/// Timers on core/async's event loop, which let a `Client` enforce its
/// per-attempt and total deadlines.
pub fn timer() -> &@rt.Timer {
@runtime.ClockTimer::new(clock=system_clock())
}
///|
/// Signals on core/async's event loop, for the parts of `gaato/mttp` that wait
/// without doing I/O themselves, such as `@endpoint.EventStream::channel`.
pub fn coordination() -> &@rt.Coordination {
@runtime.RuntimeCoordination::new()
}