# `Quiver`
[🔗](https://github.com/edlontech/quiver/blob/main/lib/quiver.ex#L1)

A mid-level HTTP client for Elixir supporting HTTP/1.1 and HTTP/2.

## Usage

    # Using the default supervisor name (Quiver.Pool):
    children = [{Quiver.Supervisor, pools: %{default: []}}]

    {:ok, %Quiver.Response{status: 200, body: body}} =
      Quiver.new(:get, "https://example.com/api")
      |> Quiver.header("authorization", "Bearer token")
      |> Quiver.request()

    # Using a custom supervisor name:
    children = [{Quiver.Supervisor, name: :my_client, pools: %{default: []}}]

    {:ok, %Quiver.Response{status: 200, body: body}} =
      Quiver.new(:get, "https://example.com/api")
      |> Quiver.request(name: :my_client)

# `body`

```elixir
@spec body(Quiver.Request.t(), iodata()) :: Quiver.Request.t()
```

Sets the request body.

Accepts any `t:iodata/0` value. Overwrites any previously set body.

## Examples

    Quiver.new(:post, "https://example.com/api")
    |> Quiver.body(~s({"key": "value"}))

    Quiver.new(:put, "https://example.com/upload")
    |> Quiver.body(["chunk1", "chunk2"])

# `default_name`

```elixir
@spec default_name() :: atom()
```

Returns the default supervisor name (`Quiver.Pool`).

# `header`

```elixir
@spec header(Quiver.Request.t(), String.t(), String.t()) :: Quiver.Request.t()
```

Appends a header to the request.

Headers are stored as a list of `{key, value}` tuples. Calling this
multiple times with the same key adds duplicate headers (does not
replace). Both key and value must be binaries.

## Examples

    request
    |> Quiver.header("authorization", "Bearer token")
    |> Quiver.header("accept", "application/json")

# `new`

```elixir
@spec new(Quiver.Conn.method(), String.t()) :: Quiver.Request.t()
```

Creates a new request with the given HTTP method and URL.

The URL is parsed into a `URI` struct and stored on the request.
Combine with `header/3`, `body/2`, and `request/2` to build and execute
the full request pipeline.

## Examples

    Quiver.new(:get, "https://example.com/api/users")

    Quiver.new(:post, "https://example.com/api/users")
    |> Quiver.header("content-type", "application/json")
    |> Quiver.body(~s({"name": "Ada"}))
    |> Quiver.request()

# `pool_stats`

```elixir
@spec pool_stats(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, :not_found}
```

Returns pool statistics for the given URL's origin.

The returned map contains:
- `:idle` -- number of idle connections/stream slots
- `:active` -- number of in-flight requests
- `:queued` -- number of callers waiting for a connection

Returns `{:error, :not_found}` if no pool exists for the origin yet.

## Options

- `:name` -- atom identifying the `Quiver.Supervisor` (default: `Quiver.Pool`)

## Examples

    {:ok, %{idle: 8, active: 2, queued: 0}} =
      Quiver.pool_stats("https://example.com")

# `request`

```elixir
@spec request(
  Quiver.Request.t(),
  keyword()
) ::
  {:ok, Quiver.Response.t()} | {:upgrade, Quiver.Upgrade.t()} | {:error, term()}
```

Executes the request and returns the full response.

The entire response body is buffered in memory. For large responses,
consider `stream_request/2` instead.

Pools are selected automatically based on the request's origin
(scheme + host + port) and the rules configured in `Quiver.Supervisor`.

## Options

- `:name` -- atom identifying the `Quiver.Supervisor` (default: `Quiver.Pool`)
- `:receive_timeout` -- max ms to wait for the response (default: 15,000)

## Examples

    {:ok, %Quiver.Response{status: 200, body: body}} =
      Quiver.new(:get, "https://example.com/api")
      |> Quiver.request()

    {:ok, resp} =
      Quiver.new(:get, "https://internal.api/data")
      |> Quiver.request(name: :internal_client, receive_timeout: 30_000)

# `stream_body`

```elixir
@spec stream_body(Quiver.Request.t(), Enumerable.t()) :: Quiver.Request.t()
```

Sets a streaming body on the request.

Wraps the given enumerable in a `{:stream, enumerable}` tagged tuple,
replacing any previously set body. The enumerable will be consumed
lazily when the request is sent.

## Examples

    Quiver.new(:post, "https://example.com/upload")
    |> Quiver.stream_body(Stream.map(chunks, &compress/1))

# `stream_request`

```elixir
@spec stream_request(
  Quiver.Request.t(),
  keyword()
) :: {:ok, Quiver.StreamResponse.t()} | {:error, term()}
```

Executes the request in streaming mode.

Returns a `Quiver.StreamResponse` with eagerly-received `status` and
`headers`, and a lazy `body` stream that yields binary chunks as the
caller enumerates it. The underlying pool connection is held for the
lifetime of the stream and released when the stream is fully consumed
or halted.

## Options

- `:name` -- atom identifying the `Quiver.Supervisor` (default: `Quiver.Pool`)
- `:receive_timeout` -- max ms to wait per response chunk (default: 15,000)

## Examples

    {:ok, %Quiver.StreamResponse{status: 200, body: body_stream}} =
      Quiver.new(:get, "https://example.com/stream/100")
      |> Quiver.stream_request()

    body_stream
    |> Stream.each(&IO.write/1)
    |> Stream.run()

---

*Consult [api-reference.md](api-reference.md) for complete listing*
