# Web Standard APIs for Elixir
A protocol-agnostic, Web API-compliant library for Elixir that mirrors the JavaScript Fetch Standard.
Built with a "Dispatcher" architecture, `Web` provides a unified interface for HTTP, TCP, and connection-string-based protocols while maintaining a zero-buffer, streaming-first approach.
## Quick Start (Script / Livebook)
You can try `Web` immediately without creating a project using `Mix.install`:
```elixir
Mix.install([
{:web, "~> 0.1.0"}
])
# 1. Construct a new Web.URL
url = Web.URL.new("https://api.github.com/search/repositories")
# 2. Modify properties via URLSearchParams
params =
Web.URL.search_params(url)
|> Web.URLSearchParams.set("q", "elixir")
|> Web.URLSearchParams.append("sort", "stars")
# 3. Apply params back to the URL
url = Web.URL.search(url, Web.URLSearchParams.to_string(params))
# 4. Construct a Web.Request with the URL
request = Web.Request.new(url,
method: "GET",
headers: %{"Accept" => "application/vnd.github.v3+json"}
)
# 5. Send to Web.fetch
{:ok, response} = Web.fetch(request)
IO.puts("Fetching: #{Web.URL.href(url)}")
IO.puts("Status: #{response.status}")
# Stream the body lazily (Zero-Buffer)
# The body is an Elixir Stream yielding chunks as they arrive from the socket
response.body
|> Stream.take(5)
|> Enum.each(&IO.write/1)
```
## Key Features
- **JS Fetch Parity**: Familiar `Request`, `Response`, and `Headers` structs.
- **Polymorphic Entry**: `Web.fetch/2` accepts a URL string, a `Web.URL`, or a `Web.Request` struct.
- **Pure Data Architecture**: `Web.URL` and `Web.URLSearchParams` are pure Elixir structs—no Agents or hidden state.
- **Overloaded Functional API**: `Web.URL.href(url, "new_val")` style setters that return new immutable structs.
- **Fetch-Style Redirects**: `Web.Dispatcher.HTTP` supports `"follow"`, `"manual"`, and `"error"` modes.
- **AbortController Support**: Standardized cancellation for in-flight fetches and active body streams.
- **Zero-Buffer Streaming**: `Web.Response.body` is an Elixir `Stream` yielding chunks directly from the socket.
- **Rclone-Style Resolution**: Native support for connection strings (`remote:path/...`).
## Installation
Add `:web` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:web, "~> 0.1.0"}
]
end
```
## Core Components
### `Request`
The `Request` struct represents a complete I/O operation. It is protocol-agnostic, allowing the same struct to be used for HTTP, TCP, or custom dispatchers.
* **Normalization**: The `:headers` field is automatically converted into a `Headers` struct.
* **Signal Support**: Pass a `AbortSignal` via the `:signal` option to enable timeouts or manual cancellation.
* **Redirect Control**: Supports `follow`, `manual`, and `error` modes.
### `Response`
The result of a successful `fetch`.
* **Streaming Body**: The `:body` is an `Enumerable` (Stream), ensuring large resources are never fully buffered into memory.
* **Metadata**: Includes the final `url` (post-redirects), `status` code, and a normalized `Headers` container.
### `Headers`
A case-insensitive, multi-value container for protocol headers.
* **Spec Parity**: Implements `append`, `set`, `get`, `delete`, and `has`.
* **Multi-Value Support**: Correctly handles multiple values for a single key, including the specific `getSetCookie` exception.
* **Protocols**: Implements `Access` and `Enumerable`, allowing for `headers["content-type"]` and `Enum.map(headers, ...)`.
### `URL` & `URLSearchParams`
Pure data structs that handle both standard URIs and rclone-style connection strings.
* **Direct Access**: Access `url.protocol`, `url.hostname`, or `url.pathname` directly via dot notation.
* **Overloaded Getters/Setters**: Use `URL.href(url, "new_url")` to parse and update the entire struct immutably.
* **Ordered Params**: `URLSearchParams` preserves key order and duplicate keys.
### AbortController` & AbortSignal`
Standardized mechanism for coordinating the cancellation of one or more asynchronous operations.
* **`AbortController`**: The management object used to trigger cancellation. Create one with AbortController.new()`.
* **`.signal` Property**: A `AbortSignal` instance linked to the controller. This is the "read-only" observer passed to `fetch` to monitor the aborted state.
* **`AbortSignal` State**: Signals include an `aborted` boolean and a `reason` for the cancellation.
* **`AbortSignal.timeout(ms)`**: Static helper that returns a signal that automatically aborts after a specified duration—perfect for handling request timeouts.
* **`AbortSignal.any(signals)`**: Static helper that combines multiple signals into one; the combined signal aborts if ANY of the provided source signals abort.
* **`AbortSignal.abort(reason)`**: Static helper that returns a signal that is already in an aborted state.
## Architecture
- **`Dispatcher`**: The core behavior for all protocol handlers.
- **`Dispatcher.HTTP`**: Powered by `Finch`, handles connection pooling and redirects internally.
- **`Dispatcher.TCP`**: Base TCP implementation using `:gen_tcp` with abort-aware streaming.
- **`Headers`**: A case-insensitive, multi-value header container with `Access` and `Enumerable` support.
## Testing
`Web` is built with a commitment to reliability, featuring 100% test coverage including property-based tests for URL parsing and header normalization.
```bash
mix test --cover
```