# Quiver
A fast, resilient HTTP client for Elixir with built-in connection pooling,
HTTP/2 multiplexing, and streaming support.
## Features
- **HTTP/1.1 and HTTP/2** -- automatic protocol handling with TLS+ALPN
- **Connection pooling** -- NimblePool for HTTP/1, GenStateMachine coordinator for HTTP/2
- **Streaming responses** -- lazy body streams for large payloads and SSE
- **Origin-based routing** -- exact, wildcard, and default pool rules per origin
- **Structured errors** -- three error classes (transient, invalid, unrecoverable)
- **Telemetry** -- request spans, connection lifecycle, and pool queue depth events
- **Supervised** -- pools start lazily and live under your application's supervision tree
## Installation
Add `quiver` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:quiver, "~> 0.1.0"}
]
end
```
## Quick Start
Add Quiver to your supervision tree:
```elixir
children = [
{Quiver.Supervisor, pools: %{default: [size: 10]}}
]
Supervisor.start_link(children, strategy: :one_for_one)
```
Make requests -- no need to pass a name, Quiver uses `Quiver.Pool` by default:
```elixir
# GET request
{:ok, %Quiver.Response{status: 200, body: body}} =
Quiver.new(:get, "https://httpbin.org/get")
|> Quiver.request()
# POST with headers and body
{:ok, %Quiver.Response{status: 200}} =
Quiver.new(:post, "https://httpbin.org/post")
|> Quiver.header("content-type", "application/json")
|> Quiver.body(~s({"key": "value"}))
|> Quiver.request()
```
Stream large responses:
```elixir
{:ok, %Quiver.StreamResponse{status: 200, body: body_stream}} =
Quiver.new(:get, "https://httpbin.org/stream/100")
|> Quiver.stream_request()
body_stream
|> Stream.each(&IO.write/1)
|> Stream.run()
```
### Custom supervisor name
If you need multiple Quiver instances, pass a `:name` option:
```elixir
# Supervision tree
children = [
{Quiver.Supervisor, name: :internal_client, pools: %{default: [size: 20]}},
{Quiver.Supervisor, name: :external_client, pools: %{default: [size: 5]}}
]
# Requests
Quiver.new(:get, "https://internal.api/data")
|> Quiver.request(name: :internal_client)
```
## Pool Configuration
Route origins to pools with different settings:
```elixir
pools = %{
"https://api.example.com" => [size: 50],
"https://*.internal.io" => [size: 20],
default: [size: 5]
}
{Quiver.Supervisor, pools: pools}
```
Rules match by specificity: exact > wildcard > default.
By default, Quiver auto-detects the HTTP protocol via TLS ALPN negotiation.
To force a specific protocol per origin:
```elixir
pools = %{
"https://http2-only.example.com" => [size: 10, protocol: :http2],
"https://legacy.example.com" => [size: 10, protocol: :http1],
default: [size: 5]
}
```
## Tesla Integration
Quiver ships with an optional [Tesla](https://github.com/elixir-tesla/tesla) adapter.
Add `tesla` to your dependencies and configure your client:
```elixir
defmodule MyClient do
use Tesla
plug Tesla.Middleware.BaseUrl, "https://api.example.com"
plug Tesla.Middleware.JSON
adapter Tesla.Adapter.Quiver
end
```
All requests go through the default `Quiver.Pool` supervisor. To target a
custom supervisor, pass it as an adapter option:
```elixir
adapter Tesla.Adapter.Quiver, name: :my_quiver
```
Adapter-level options like streaming and timeouts can also be passed
per-request:
```elixir
MyClient.get("/large-file", opts: [adapter: [response: :stream]])
MyClient.get("/slow", opts: [adapter: [receive_timeout: 60_000]])
```
See `Tesla.Adapter.Quiver` docs for all available options.
## Documentation
- [Getting Started](guides/getting-started.md)
- [Architecture](guides/architecture.md)
- [Error Handling](guides/error-handling.md)
- [Telemetry](guides/telemetry.md)
Full API documentation is available on [HexDocs](https://hexdocs.pm/quiver).
## License
MIT -- see [LICENSE](LICENSE) for details.