# Request & Response

Every HTTP handler in Effect does two things: it reads from the incoming
**request** and returns a **response**. Both are plain immutable values backed by
a small set of shared data types — `Headers`, `UrlParams`, `Url`, `HttpBody`, and
`Cookies` — that the same modules use on the client side too.

The request is read from context through the `HttpServerRequest` service. The
response is built with the `HttpServerResponse` constructors and combinators, all
of which are immutable: every combinator returns a new response.

```ts
import { Effect, Schema } from "effect"
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"

const CreateUser = Schema.Struct({
  name: Schema.String,
  email: Schema.String
})

// A handler: read the JSON body with a schema, return a JSON response
const handler = Effect.gen(function* () {
  // Read request metadata directly from the service
  const request = yield* HttpServerRequest.HttpServerRequest
  console.log(request.method) // => "POST"
  console.log(request.url) // => "/users"

  // Decode the body against a schema (fails with SchemaError on bad input)
  const body = yield* HttpServerRequest.schemaBodyJson(CreateUser)

  // Build a 201 JSON response
  return yield* HttpServerResponse.json(
    { id: 1, ...body },
    { status: 201 }
  )
})
```
**Unstable module:** The HTTP modules live under `effect/unstable/http`. Import from the group
  barrel (``) or from the
  specific module path (`effect/unstable/http/HttpServerRequest`).

## Reading the request

`HttpServerRequest.HttpServerRequest` is a `Context.Service`. Yielding it gives
you the request value, whose metadata is available synchronously while body
access is effectful (reading and parsing can fail).

```ts
import { Effect, Option } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const inspect = Effect.gen(function* () {
  const req = yield* HttpServerRequest.HttpServerRequest

  req.method // => "GET" | "POST" | ... (HttpMethod)
  req.url // => "/search?q=effect" (path without scheme/host)
  req.originalUrl // => the full original URL string
  req.headers // => Headers (lowercase-keyed record)
  req.cookies // => { session: "abc123" } (parsed from the Cookie header)
  req.remoteAddress // => Option<string>

  // Body accessors are Effects (inherited from HttpIncomingMessage)
  const text = yield* req.text // => string
  const json = yield* req.json // => parsed JSON value
  const buffer = yield* req.arrayBuffer // => ArrayBuffer
  req.stream // => Stream<Uint8Array, HttpServerError>

  // Derive a modified view without mutating the original
  const rewritten = req.modify({
    url: "/v2" + req.url,
    remoteAddress: Option.some("127.0.0.1")
  })
  return rewritten
})
```
**Bodies may be single-use:** Depending on the platform, the raw request body stream can be consumed only
  once. The cached accessors (`text`, `json`, `arrayBuffer`, and persisted
  multipart) reuse the first read, so prefer those over reading `stream`
  directly more than once.

### Decoding the body with schemas

Rather than parse raw values by hand, decode directly into a typed value. Each
helper is an `Effect` that fails with `Schema.SchemaError` (and `HttpServerError`
when reading the body itself can fail).

```ts
import { Effect, Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const Login = Schema.Struct({
  username: Schema.String,
  password: Schema.String
})

// JSON body → typed value
const fromJson = HttpServerRequest.schemaBodyJson(Login)

// application/x-www-form-urlencoded body → typed value
const fromForm = HttpServerRequest.schemaBodyUrlParams(Login)

// Either multipart OR urlencoded form, chosen by content-type
const fromAnyForm = HttpServerRequest.schemaBodyForm(Login)
```

### `HttpServerRequest`

The service tag and the request model. Yield the tag to get the current request;
its fields are described above.

```ts
import { Effect } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const method = Effect.map(
  HttpServerRequest.HttpServerRequest,
  (req) => req.method
)
// => Effect<HttpMethod, never, HttpServerRequest>
```

### `schemaBodyJson`

Reads the request body as JSON and decodes it with the supplied schema.

```ts
import { Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const User = Schema.Struct({ id: Schema.Number, name: Schema.String })
const decoded = HttpServerRequest.schemaBodyJson(User)
// => Effect<{ id: number; name: string }, HttpServerError | SchemaError, HttpServerRequest>
```

### `schemaBodyForm`

Decodes the body as form data: multipart requests are persisted and decoded as
multipart, other requests are decoded from URL-encoded parameters. Requires
`Scope`, `FileSystem`, and `Path` for the multipart path.

```ts
import { Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const Form = Schema.Struct({ title: Schema.String })
const decoded = HttpServerRequest.schemaBodyForm(Form)
// => Effect<{ title: string }, MultipartError | SchemaError | HttpServerError, ...>
```

### `schemaBodyFormJson`

Builds a decoder that reads a JSON value out of a single named form field (works
for both multipart fields and URL-encoded parameters).

```ts
import { Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const Payload = Schema.Struct({ tags: Schema.Array(Schema.String) })
const decodeField = HttpServerRequest.schemaBodyFormJson(Payload)
const decoded = decodeField("metadata") // reads the "metadata" field as JSON
```

### `schemaBodyUrlParams`

Reads the body as `application/x-www-form-urlencoded` parameters and decodes
them with the schema.

```ts
import { Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const Search = Schema.Struct({ q: Schema.String })
const decoded = HttpServerRequest.schemaBodyUrlParams(Search)
// => Effect<{ q: string }, HttpServerError | SchemaError, HttpServerRequest>
```

### `schemaBodyMultipart`

Persists the multipart body and decodes it with the schema. Requires `Scope`,
`FileSystem`, and `Path` to persist uploaded files.

```ts
import { Schema } from "effect"
import { HttpServerRequest, Multipart } from "effect/unstable/http"

const Upload = Schema.Struct({
  title: Schema.String,
  file: Multipart.SingleFileSchema
})
const decoded = HttpServerRequest.schemaBodyMultipart(Upload)
```

### `schemaCookies`

Decodes a schema from the request's parsed cookies.

```ts
import { Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const Session = Schema.Struct({ session: Schema.String })
const cookies = HttpServerRequest.schemaCookies(Session)
// => Effect<{ session: string }, SchemaError, HttpServerRequest>
```

### `schemaHeaders`

Decodes a schema from the request headers (header names are lowercase).

```ts
import { Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const Auth = Schema.Struct({ authorization: Schema.String })
const headers = HttpServerRequest.schemaHeaders(Auth)
// => Effect<{ authorization: string }, SchemaError, HttpServerRequest>
```

### `schemaSearchParams`

Decodes a schema from the parsed query string. Requires the `ParsedSearchParams`
service (provided by the router / adapter).

```ts
import { Schema } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const Query = Schema.Struct({ q: Schema.String, page: Schema.optional(Schema.String) })
const params = HttpServerRequest.schemaSearchParams(Query)
// => Effect<{ q: string; page?: string }, SchemaError, ParsedSearchParams>
```

### `ParsedSearchParams`

`Context.Service` holding the decoded query parameters for the current request.
Each key maps to a string, or an array when the parameter repeats.

```ts
import { Effect } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const all = HttpServerRequest.ParsedSearchParams
// => Effect<ReadonlyRecord<string, string | Array<string>>, never, ParsedSearchParams>
```

### `searchParamsFromURL`

Converts a `URL`'s search parameters into a record; repeated keys become arrays
in insertion order.

```ts
import { HttpServerRequest } from "effect/unstable/http"

HttpServerRequest.searchParamsFromURL(new URL("https://x.dev/?a=1&a=2&b=3"))
// => { a: ["1", "2"], b: "3" }
```

### `upgradeChannel`

Creates a `Channel` backed by the current request's upgraded socket — the
building block for WebSocket / raw socket handlers.

```ts
import { HttpServerRequest } from "effect/unstable/http"

const channel = HttpServerRequest.upgradeChannel()
// => Channel reading socket frames and writing string | Uint8Array | CloseEvent
```

The lower-level `request.upgrade` accessor yields the `Socket` directly:

```ts
import { Effect } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const socket = Effect.flatMap(
  HttpServerRequest.HttpServerRequest,
  (req) => req.upgrade // => Effect<Socket, HttpServerError>
)
```

### `fromWeb` / `fromClientRequest`

Wrap a Web `Request` (or an `HttpClientRequest`) as an `HttpServerRequest` — handy
for adapters and tests.

```ts
import { HttpServerRequest } from "effect/unstable/http"

const req = HttpServerRequest.fromWeb(
  new Request("https://example.com/users", { method: "POST" })
)
req.method // => "POST"
req.url // => "/users"
```

### `toWeb` / `toWebResult`

Convert an `HttpServerRequest` to a Web `Request`. `toWebResult` returns a
`Result` synchronously; `toWeb` returns an `Effect` that streams the body using
the current context.

```ts
import { Effect } from "effect"
import { HttpServerRequest } from "effect/unstable/http"

const program = Effect.gen(function* () {
  const req = yield* HttpServerRequest.HttpServerRequest
  const web = yield* HttpServerRequest.toWeb(req)
  return web // => globalThis.Request
})
```

### `toClientRequest`

Convert a server request into an `HttpClientRequest`, preserving method, headers,
and body — useful for proxying.

```ts
import { HttpServerRequest } from "effect/unstable/http"

declare const req: HttpServerRequest.HttpServerRequest
const clientReq = HttpServerRequest.toClientRequest(req)
```

### `toURL`

Builds an absolute `URL` for the request (host from the `host` header, defaulting
to `localhost`; `https` only when `x-forwarded-proto` is `https`). Returns
`Option.none()` for invalid URLs.

```ts
import { HttpServerRequest } from "effect/unstable/http"

declare const req: HttpServerRequest.HttpServerRequest
HttpServerRequest.toURL(req) // => Option<URL>
```

### `MaxBodySize`

Re-exported fiber reference controlling the maximum body size accepted while
reading request bodies. Set it with `Effect.provideService` (or the fiber-ref
combinators) around your handler.

## Building responses

`HttpServerResponse` values are immutable. A response is a `status`, optional
`statusText`, `headers`, `cookies`, and an `HttpBody`. Constructors set practical
defaults — `empty` is `204`, `redirect` is `302`, and body constructors are `200`
— and reflect body metadata (content type, content length) into headers.

```ts
import { HttpServerResponse } from "effect/unstable/http"

// 200 text/plain
const ok = HttpServerResponse.text("hello")

// 201 with a header, then a cookie (unsafe variant throws on invalid cookies)
const created = HttpServerResponse.text("created", { status: 201 }).pipe(
  HttpServerResponse.setHeader("x-request-id", "abc"),
  HttpServerResponse.setCookieUnsafe("seen", "1", { path: "/" })
)

// 302 redirect
const away = HttpServerResponse.redirect("/login")
```

Most constructors accept an `Options` object:

```ts
interface Options {
  readonly status?: number | undefined
  readonly statusText?: string | undefined
  readonly headers?: Headers.Input | undefined
  readonly cookies?: Cookies.Cookies | undefined
  readonly contentType?: string | undefined
  readonly contentLength?: number | undefined
}
```

Body-aware constructors narrow this: `Options.WithContent` omits
`contentType`/`contentLength` (the body decides them), and `Options.WithContentType`
omits only `contentLength`.
**Cookies are not regular headers:** `Set-Cookie` headers are produced from the response's separate cookie
  collection during conversion, not by editing the header map. Use the cookie
  combinators (`setCookie`, `expireCookie`, …) rather than `setHeader("set-cookie", …)`.

### `empty`

An empty response, defaulting to status `204`.

```ts
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.empty() // => 204, no body
HttpServerResponse.empty({ status: 304 }) // => 304
```

### `text`

A `text/plain` response from a string.

```ts
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.text("pong")
// => 200, content-type: text/plain
```

### `json`

A JSON response built in `Effect`; serialization failures become `HttpBodyError`.

```ts
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.json({ ok: true })
// => Effect<HttpServerResponse, HttpBodyError>
```

### `jsonUnsafe`

A JSON response built synchronously; throws if `JSON.stringify` fails.

```ts
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.jsonUnsafe({ ok: true })
// => HttpServerResponse (200, application/json)
```

### `schemaJson`

Returns a JSON-response constructor that first encodes the value with a schema.
Can fail with `HttpBodyError` on encoding or serialization errors.

```ts
import { Schema } from "effect"
import { HttpServerResponse } from "effect/unstable/http"

const User = Schema.Struct({ id: Schema.Number, name: Schema.String })
const userResponse = HttpServerResponse.schemaJson(User)

userResponse({ id: 1, name: "Ada" }, { status: 201 })
// => Effect<HttpServerResponse, HttpBodyError>
```

### `html`

A `text/html` response. Pass a string for a direct response, or use it as a
template tag to render interpolated values (returning an `Effect`).

```ts
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.html("<h1>Hi</h1>") // => HttpServerResponse
HttpServerResponse.html`<h1>${"Ada"}</h1>` // => Effect<HttpServerResponse, ...>
```

### `htmlStream`

A streaming `text/html` response from a template, encoding the template as a byte
stream and supporting streaming interpolated values.

```ts
import { Stream } from "effect"
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.htmlStream`<ul>${Stream.make("<li>a</li>", "<li>b</li>")}</ul>`
// => Effect<HttpServerResponse, never, ...>
```

### `uint8Array`

A response whose body is raw bytes (defaults to `application/octet-stream`).

```ts
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.uint8Array(new Uint8Array([1, 2, 3]))
// => 200, content-type: application/octet-stream
```

### `raw`

A response with a platform-native body value (a Web `Response`, `Blob`, or
`ReadableStream`) passed through to the adapter.

```ts
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.raw(new Blob(["data"]), { contentType: "text/plain" })
```

### `stream`

A streaming response from a `Stream<Uint8Array, E>`.

```ts
import { Stream } from "effect"
import { HttpServerResponse } from "effect/unstable/http"

const bytes = Stream.make(new Uint8Array([72, 105])) // "Hi"
HttpServerResponse.stream(bytes, { contentType: "text/plain" })
```

### `formData`

A response whose body is a Web `FormData` value (the runtime supplies the
multipart boundary).

```ts
import { HttpServerResponse } from "effect/unstable/http"

const fd = new FormData()
fd.append("name", "Ada")
HttpServerResponse.formData(fd)
```

### `urlParams`

A response encoded as `application/x-www-form-urlencoded`.

```ts
import { HttpServerResponse, UrlParams } from "effect/unstable/http"

HttpServerResponse.urlParams(UrlParams.fromInput({ q: "effect", page: 1 }))
// => body: "q=effect&page=1"
```

### `file`

A streamed file response for a filesystem path. Requires `HttpPlatform`; can fail
with `PlatformError`. Supports `offset`, `bytesToRead`, and `chunkSize`.

```ts
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.file("/var/www/report.pdf", { contentType: "application/pdf" })
// => Effect<HttpServerResponse, PlatformError, HttpPlatform>
```

### `fileWeb`

A streamed file response for a Web `File`-like value. Requires `HttpPlatform`.

```ts
import { HttpServerResponse } from "effect/unstable/http"

declare const file: File
HttpServerResponse.fileWeb(file)
// => Effect<HttpServerResponse, never, HttpPlatform>
```

### `setStatus`

Returns a response with a new status code (and optional status text).

```ts
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.text("nope").pipe(HttpServerResponse.setStatus(403, "Forbidden"))
// => status 403, statusText "Forbidden"
```

### `setHeader` / `setHeaders`

Set one header, or set many at once.

```ts
import { HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.empty().pipe(
  HttpServerResponse.setHeader("cache-control", "no-store"),
  HttpServerResponse.setHeaders({ "x-a": "1", "x-b": "2" })
)
```

### `setBody`

Replace the response body. Content-type / content-length headers follow the new
body's metadata.

```ts
import { HttpBody, HttpServerResponse } from "effect/unstable/http"

HttpServerResponse.empty().pipe(
  HttpServerResponse.setBody(HttpBody.text("replaced"))
)
```

### Cookie combinators

Cookies are managed on the response's cookie collection. The safe variants return
an `Effect` failing with `CookiesError`; the `*Unsafe` variants throw.

```ts
import { Effect } from "effect"
import { Cookies, HttpServerResponse } from "effect/unstable/http"

const withCookies = Effect.gen(function* () {
  let res = HttpServerResponse.empty()

  // setCookie / setCookieUnsafe: add one cookie
  res = yield* HttpServerResponse.setCookie(res, "session", "abc123", {
    httpOnly: true,
    sameSite: "lax",
    path: "/"
  })
  res = HttpServerResponse.setCookieUnsafe(res, "theme", "dark")

  // setCookies / setCookiesUnsafe: add many at once
  res = yield* HttpServerResponse.setCookies(res, [
    ["a", "1"],
    ["b", "2", { path: "/" }]
  ])
  res = HttpServerResponse.setCookiesUnsafe(res, [["c", "3"]])

  // expireCookie / expireCookieUnsafe: emit an already-expired cookie
  res = yield* HttpServerResponse.expireCookie(res, "session", { path: "/" })
  res = HttpServerResponse.expireCookieUnsafe(res, "theme")

  // collection-level operations
  res = HttpServerResponse.removeCookie(res, "a") // drop one cookie
  res = HttpServerResponse.mergeCookies(res, Cookies.empty) // merge in a collection
  res = HttpServerResponse.replaceCookies(res, Cookies.empty) // replace all
  res = HttpServerResponse.updateCookies(res, (c) => Cookies.remove(c, "b")) // edit via fn

  return res
})
```

| Combinator | Effectful? | Description |
| --- | --- | --- |
| `setCookie` | yes | Add one cookie, validating it |
| `setCookieUnsafe` | no | Add one cookie, throwing on invalid input |
| `setCookies` | yes | Add many cookies from `[name, value, options?]` tuples |
| `setCookiesUnsafe` | no | Add many cookies, throwing on invalid input |
| `expireCookie` | yes | Add an expired cookie (empty value, `Max-Age=0`) |
| `expireCookieUnsafe` | no | Expire a cookie, throwing on invalid input |
| `removeCookie` | no | Remove one cookie from the collection |
| `mergeCookies` | no | Merge another `Cookies` collection in |
| `replaceCookies` | no | Replace the whole cookie collection |
| `updateCookies` | no | Map the cookie collection with a function |

### Converting responses

`toWeb` produces a Web `Response` (cookies become `Set-Cookie` headers). `fromWeb`
wraps a Web `Response`. `toClientResponse` / `fromClientResponse` bridge to the
client model.

```ts
import { HttpServerResponse } from "effect/unstable/http"

const res = HttpServerResponse.text("hi")
const web = HttpServerResponse.toWeb(res) // => globalThis.Response
const back = HttpServerResponse.fromWeb(web) // => HttpServerResponse
```

`isHttpServerResponse` is the runtime guard.

## Respondable

`HttpServerRespondable.Respondable` lets a value describe its own response. Domain
errors and API errors commonly implement it so error handling can ask the value
how to render itself instead of building a response at every call site.

```ts
import { Effect } from "effect"
import { HttpServerRespondable, HttpServerResponse } from "effect/unstable/http"

class NotFoundError {
  // The protocol method returns Effect<HttpServerResponse, unknown>
  [HttpServerRespondable.symbol]() {
    return Effect.succeed(HttpServerResponse.text("not found", { status: 404 }))
  }
}

HttpServerRespondable.isRespondable(new NotFoundError()) // => true
```

### `toResponse`

Converts a `Respondable` (or an existing `HttpServerResponse`) into a response.
Conversion failures become defects.

```ts
import { HttpServerRespondable } from "effect/unstable/http"

declare const value: HttpServerRespondable.Respondable
HttpServerRespondable.toResponse(value)
// => Effect<HttpServerResponse>
```

The module also exposes `toResponseOrElse` and `toResponseOrElseDefect`, which
fall back to a supplied response for unknown failures — mapping `SchemaError` to
`400` and `NoSuchElementError` to `404`.

## HttpBody reference

`HttpBody` is the transport-facing body representation shared by requests and
responses. You rarely build one directly (the response constructors do it for
you), but the constructors are useful with `setBody` and when proxying. Variants:
`Empty`, `Raw`, `Uint8Array`, `FormData`, `Stream`.

### `empty`

The singleton empty body.

```ts
import { HttpBody } from "effect/unstable/http"

HttpBody.empty._tag // => "Empty"
```

### `text`

UTF-8 text body, defaulting to `text/plain`.

```ts
import { HttpBody } from "effect/unstable/http"

HttpBody.text("hello").contentType // => "text/plain"
```

### `json`

JSON body in `Effect`; `JSON.stringify` failures become `HttpBodyError`.

```ts
import { HttpBody } from "effect/unstable/http"

HttpBody.json({ a: 1 }) // => Effect<HttpBody.Uint8Array, HttpBodyError>
```

### `jsonUnsafe`

JSON body built synchronously; throws on serialization failure.

```ts
import { HttpBody } from "effect/unstable/http"

HttpBody.jsonUnsafe({ a: 1 }).contentType // => "application/json"
```

### `uint8Array`

Byte-array body, defaulting to `application/octet-stream`.

```ts
import { HttpBody } from "effect/unstable/http"

HttpBody.uint8Array(new Uint8Array([1, 2])).contentLength // => 2
```

### `raw`

Wraps an arbitrary runtime body value with optional content metadata.

```ts
import { HttpBody } from "effect/unstable/http"

HttpBody.raw(new Blob(["x"]), { contentType: "text/plain", contentLength: 1 })
```

### `stream`

Streaming body from a `Stream<Uint8Array>`, defaulting to
`application/octet-stream`; content length is optional.

```ts
import { Stream } from "effect"
import { HttpBody } from "effect/unstable/http"

HttpBody.stream(Stream.make(new Uint8Array([1])), "text/plain")
```

### `formData`

Wraps a Web `FormData` value as a body (content type left unset for the boundary).

```ts
import { HttpBody } from "effect/unstable/http"

const fd = new FormData()
fd.append("name", "Ada")
HttpBody.formData(fd)
```

### `formDataRecord`

Builds a `FormData` body from a record. Arrays append repeated keys, primitives
are stringified, `File`/`Blob` values are appended directly, nullish values are
skipped.

```ts
import { HttpBody } from "effect/unstable/http"

HttpBody.formDataRecord({ name: "Ada", tags: ["a", "b"], skip: null })
// => FormData with name=Ada, tags=a, tags=b
```

### `urlParams`

`application/x-www-form-urlencoded` body from `UrlParams`.

```ts
import { HttpBody, UrlParams } from "effect/unstable/http"

HttpBody.urlParams(UrlParams.fromInput({ q: "effect" }))
// => contentType: application/x-www-form-urlencoded
```

### `file` / `fileFromInfo`

Stream a file from a path. `file` stats the file to set content length;
`fileFromInfo` reuses already-known `File.Info`. Both require `FileSystem` and may
fail with `PlatformError`.

```ts
import { HttpBody } from "effect/unstable/http"

HttpBody.file("/tmp/data.bin")
// => Effect<HttpBody.Stream, PlatformError, FileSystem>
```

### Body classes & `HttpBodyError`

`Empty`, `Raw`, `Uint8Array`, `FormData`, and `Stream` are the body variant
classes (each carries a `_tag`). `isHttpBody` is the runtime guard. `HttpBodyError`
is a tagged error with a `reason` of `JsonError` or `SchemaError`.

```ts
import { HttpBody } from "effect/unstable/http"

const body = HttpBody.text("hi")
HttpBody.isHttpBody(body) // => true
body._tag // => "Uint8Array"
```

## Headers reference

`Headers` is an immutable, lowercase-keyed string map. Names are normalized
because HTTP header names are case-insensitive, and combinators return new
collections.

```ts
import { Option } from "effect"
import { Headers } from "effect/unstable/http"

const h = Headers.fromInput({
  "Content-Type": "application/json",
  accept: ["application/json", "text/plain"]
})
Headers.get(h, "content-type") // => Option.some("application/json")
h["accept"] // => "application/json, text/plain" (array values joined with ", ")
```

### `empty`

The empty header collection.

```ts
import { Headers } from "effect/unstable/http"

Headers.empty // => {} (Headers)
```

### `fromInput`

Builds `Headers` from a record or iterable of entries; names are lowercased,
array values joined with `", "`, `undefined` omitted.

```ts
import { Headers } from "effect/unstable/http"

Headers.fromInput({ "X-Trace": "1", drop: undefined })["x-trace"] // => "1"
```

### `fromRecordUnsafe`

Treats an existing record as `Headers` without normalizing names. Caller must
supply lowercase keys.

```ts
import { Headers } from "effect/unstable/http"

Headers.fromRecordUnsafe({ "content-type": "text/plain" })
```

### `get`

Reads a header value safely (lowercases the lookup).

```ts
import { Headers } from "effect/unstable/http"

Headers.get(Headers.fromInput({ a: "1" }), "A") // => Option.some("1")
```

### `has`

Returns whether a header is present.

```ts
import { Headers } from "effect/unstable/http"

Headers.has(Headers.fromInput({ a: "1" }), "A") // => true
```

### `set`

Returns a new collection with one header set.

```ts
import { Headers } from "effect/unstable/http"

Headers.set(Headers.empty, "Authorization", "Bearer x")["authorization"]
// => "Bearer x"
```

### `setAll`

Sets many headers at once (input normalized via `fromInput`).

```ts
import { Headers } from "effect/unstable/http"

Headers.setAll(Headers.empty, { "X-A": "1", "X-B": "2" })
```

### `merge`

Merges two `Headers`; the second wins on conflicts.

```ts
import { Headers } from "effect/unstable/http"

Headers.merge(Headers.fromInput({ a: "1" }), Headers.fromInput({ a: "2", b: "3" }))
// => { a: "2", b: "3" }
```

### `remove`

Removes one header by name.

```ts
import { Headers } from "effect/unstable/http"

Headers.remove(Headers.fromInput({ a: "1", b: "2" }), "A") // => { b: "2" }
```

### `removeMany`

Removes several headers by name.

```ts
import { Headers } from "effect/unstable/http"

Headers.removeMany(Headers.fromInput({ a: "1", b: "2", c: "3" }), ["a", "c"])
// => { b: "2" }
```

### `redact` & `CurrentRedactedNames`

`redact` returns a record with selected header values wrapped in `Redacted`.
`CurrentRedactedNames` is the context reference listing which names are masked on
inspection (defaults: `authorization`, `cookie`, `set-cookie`, `x-api-key`).

```ts
import { Headers } from "effect/unstable/http"

Headers.redact(Headers.fromInput({ authorization: "secret", a: "1" }), "authorization")
// => { authorization: Redacted(<redacted>), a: "1" }
```

### `Equivalence`, `HeadersSchema`, `isHeaders`

`Equivalence` compares two `Headers` by names and values; `HeadersSchema` is the
schema encoding `Headers` as a string record; `isHeaders` is the runtime guard.

```ts
import { Headers } from "effect/unstable/http"

Headers.isHeaders(Headers.empty) // => true
```

## UrlParams reference

`UrlParams` is an ordered list of `[key, value]` string pairs that preserves
duplicate keys and order. Used for query strings and URL-encoded bodies.

### `empty`

An empty `UrlParams`.

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.empty.params // => []
```

### `make`

Builds from ordered string pairs as-is (no coercion).

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.make([["a", "1"], ["a", "2"]]) // keeps both "a" pairs
```

### `fromInput`

Builds from a record, iterable, or `URLSearchParams`. Primitives become strings,
arrays become repeated params, nested records use bracket notation, `undefined`
omitted.

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.toString(UrlParams.fromInput({ a: 1, tags: ["x", "y"] }))
// => "a=1&tags=x&tags=y"
```

### `set`

Replaces all values for a key with a single value (appended at the end).

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.toString(UrlParams.set(UrlParams.fromInput({ a: "1" }), "a", "2"))
// => "a=2"
```

### `setAll`

Replaces values for the keys present in the input; other params are preserved.

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.toString(UrlParams.setAll(UrlParams.fromInput({ a: "1", b: "2" }), { a: "9" }))
// => "a=9&b=2"
```

### `append`

Appends a value without removing existing values for the key.

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.toString(UrlParams.append(UrlParams.fromInput({ a: "1" }), "a", "2"))
// => "a=1&a=2"
```

### `appendAll`

Appends all params from the input, preserving existing ones.

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.toString(UrlParams.appendAll(UrlParams.fromInput({ a: "1" }), { b: "2" }))
// => "a=1&b=2"
```

### `remove`

Removes all values for a key.

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.toString(UrlParams.remove(UrlParams.fromInput({ a: "1", b: "2" }), "a"))
// => "b=2"
```

### `getAll` / `getFirst` / `getLast`

Read all values for a key, or the first / last value as an `Option`.

```ts
import { UrlParams } from "effect/unstable/http"

const p = UrlParams.fromInput({ a: ["1", "2"] })
UrlParams.getAll(p, "a") // => ["1", "2"]
UrlParams.getFirst(p, "a") // => Option.some("1")
UrlParams.getLast(p, "a") // => Option.some("2")
```

### `toRecord` / `toReadonlyRecord`

Build a record; single-value keys map to a string, repeated keys to a non-empty
array.

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.toRecord(UrlParams.fromInput({ a: 1, e: [1, 2, 3] }))
// => { a: "1", e: ["1", "2", "3"] }
```

### `toString`

Serializes to a query string (no leading `?`).

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.toString(UrlParams.fromInput({ q: "a b" })) // => "q=a+b"
```

### `makeUrl`

Builds a `URL` from a base string plus params and an optional hash; returns a
`Result` failing with `UrlParamsError`.

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.makeUrl("https://x.dev/search", UrlParams.fromInput({ q: "effect" }), undefined)
// => Result.success(URL "https://x.dev/search?q=effect")
```

### `transform`

Maps the underlying ordered pairs.

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.transform(
  UrlParams.fromInput({ a: "1" }),
  (pairs) => [...pairs, ["b", "2"]]
)
```

### `schemaRecord` / `schemaJsonField`

`schemaRecord` decodes `UrlParams` into a record schema; `schemaJsonField` reads
one field's value as JSON. Both compose with `Schema.decodeTo`.

```ts
import { Schema } from "effect"
import { UrlParams } from "effect/unstable/http"

const toStruct = UrlParams.schemaRecord.pipe(
  Schema.decodeTo(Schema.Struct({ some: Schema.String, number: Schema.FiniteFromString }))
)
Schema.decodeSync(toStruct)(UrlParams.fromInput({ some: "value", number: 42 }))
// => { some: "value", number: 42 }
```

### `Equivalence`, `UrlParamsSchema`, `UrlParamsError`, `isUrlParams`

`Equivalence` is order-sensitive; `UrlParamsSchema` encodes `UrlParams` as an
array of string tuples; `UrlParamsError` is the tagged error from `makeUrl`;
`isUrlParams` is the guard.

```ts
import { UrlParams } from "effect/unstable/http"

UrlParams.isUrlParams(UrlParams.empty) // => true
```

## Url reference

The `Url` module keeps the WHATWG `URL` as the representation and adds safe
parsing plus immutable, pipeable setters. Setters never mutate the input; each
clones the `URL` and assigns on the clone.

### `fromString`

Parses a URL string safely, returning a `Result` (fails with
`IllegalArgumentError`). Accepts an optional base for relative URLs.

```ts
import { Url } from "effect/unstable/http"

Url.fromString("/path", "https://example.com")
// => Result.success(URL "https://example.com/path")
```

### `mutate`

Applies a callback to a clone of the URL, allowing several assignments at once.

```ts
import { Url } from "effect/unstable/http"

Url.mutate(new URL("https://example.com"), (url) => {
  url.username = "user"
  url.password = "pass"
}).toString()
// => "https://user:pass@example.com/"
```

### `urlParams`

Reads the query parameters as `UrlParams`.

```ts
import { Url } from "effect/unstable/http"

Url.urlParams(new URL("https://example.com?foo=bar")).params
// => [["foo", "bar"]]
```

### `setUrlParams`

Replaces the query parameters with the given `UrlParams`.

```ts
import { Url, UrlParams } from "effect/unstable/http"

Url.setUrlParams(new URL("https://example.com?foo=bar"), UrlParams.fromInput({ key: "value" })).toString()
// => "https://example.com/?key=value"
```

### `modifyUrlParams`

Reads the params, applies a function, and writes them back.

```ts
import { Url, UrlParams } from "effect/unstable/http"

Url.modifyUrlParams(new URL("https://example.com?foo=bar"), UrlParams.append("key", "value")).toString()
// => "https://example.com/?foo=bar&key=value"
```

### Component setters

Pipeable setters for each `URL` component. `setPassword` also accepts `Redacted`
input (the resulting URL still contains the real credential).

```ts
import { Url } from "effect/unstable/http"

const base = new URL("https://example.com")
Url.setHash(base, "#section") // url.hash
Url.setHost(base, "api.example.com:8080") // host (domain + port)
Url.setHostname(base, "api.example.com") // domain only
Url.setHref(base, "https://other.dev/") // replace whole URL
Url.setPassword(base, "pass") // password (string | Redacted)
Url.setPathname(base, "/v1/users") // path
Url.setPort(base, 8080) // port (string | number)
Url.setProtocol(base, "http:") // protocol
Url.setSearch(base, "?q=1") // query string
Url.setUsername(base, "user") // username
```

## Cookies reference

`Cookies` is an immutable collection keyed by cookie name (at most one cookie per
name). A `Cookie` stores both the decoded `value` and the encoded `valueEncoded`.

```ts
import { Cookies } from "effect/unstable/http"

const cookies = Cookies.setUnsafe(Cookies.empty, "session", "abc123", {
  httpOnly: true,
  path: "/",
  sameSite: "lax",
  secure: true
})
Cookies.toSetCookieHeaders(cookies)
// => ["session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax"]
```

### `empty`

The empty cookie collection.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.isEmpty(Cookies.empty) // => true
```

### `fromIterable` / `fromReadonlyRecord` / `fromSetCookie`

Build a collection from an iterable of `Cookie`, a record keyed by name, or a set
of `Set-Cookie` header strings.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.fromSetCookie(["a=1; Path=/", "b=2"])
// => Cookies containing a and b
```

### `makeCookie` / `makeCookieUnsafe`

Create a single `Cookie`, validating name, value, domain, path, and finite
max-age. `makeCookie` returns a `Result`; the unsafe variant throws.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.makeCookie("a", "1", { path: "/" })
// => Result.success(Cookie)
Cookies.makeCookieUnsafe("a", "1") // => Cookie (throws on invalid input)
```

### `set` / `setUnsafe`

Create and add a cookie by name and value. `set` returns a `Result`; `setUnsafe`
throws.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.set(Cookies.empty, "a", "1") // => Result.success(Cookies)
Cookies.setUnsafe(Cookies.empty, "a", "1") // => Cookies
```

### `setAll` / `setAllUnsafe`

Create and add many cookies from `[name, value, options?]` tuples. `setAll`
returns the first `CookiesError` on failure; `setAllUnsafe` throws.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.setAll(Cookies.empty, [["a", "1"], ["b", "2", { path: "/" }]])
// => Result.success(Cookies)
```

### `setCookie` / `setAllCookie`

Add already-built `Cookie` values (one, or an iterable).

```ts
import { Cookies } from "effect/unstable/http"

const a = Cookies.makeCookieUnsafe("a", "1")
Cookies.setCookie(Cookies.empty, a) // add one Cookie
Cookies.setAllCookie(Cookies.empty, [a]) // add many Cookies
```

### `get` / `getValue`

Look up a `Cookie` (or its decoded value) by name as an `Option`.

```ts
import { Cookies } from "effect/unstable/http"

const c = Cookies.setUnsafe(Cookies.empty, "a", "1")
Cookies.get(c, "a") // => Option.some(Cookie)
Cookies.getValue(c, "a") // => Option.some("1")
```

### `merge`

Combine two collections; the second collection wins on name conflicts.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.merge(
  Cookies.setUnsafe(Cookies.empty, "a", "1"),
  Cookies.setUnsafe(Cookies.empty, "b", "2")
)
```

### `remove`

Remove a cookie by name.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.remove(Cookies.setUnsafe(Cookies.empty, "a", "1"), "a")
// => empty collection
```

### `expireCookie` / `expireCookieUnsafe`

Add an expired cookie (empty value, `Max-Age=0`, epoch `Expires`). `expireCookie`
returns a `Result`; the unsafe variant throws.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.expireCookieUnsafe(Cookies.empty, "session", { path: "/" })
// => Set-Cookie: session=; Max-Age=0; Path=/; Expires=...
```

### `toCookieHeader` / `toSetCookieHeaders` / `toRecord`

`toCookieHeader` builds an outbound request `Cookie` header. `toSetCookieHeaders`
builds response `Set-Cookie` header strings. `toRecord` returns decoded values
keyed by name.

```ts
import { Cookies } from "effect/unstable/http"

const c = Cookies.setAllUnsafe(Cookies.empty, [["a", "1"], ["b", "2"]])
Cookies.toCookieHeader(c) // => "a=1; b=2"
Cookies.toSetCookieHeaders(c) // => ["a=1", "b=2"]
Cookies.toRecord(c) // => { a: "1", b: "2" }
```

### `parseHeader`

Parses a request `Cookie` header into a record of name/value pairs.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.parseHeader("a=1; b=2") // => { a: "1", b: "2" }
```

### `serializeCookie`

Serializes a single `Cookie` into its `Set-Cookie` string.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.serializeCookie(Cookies.makeCookieUnsafe("a", "1", { path: "/", httpOnly: true }))
// => "a=1; Path=/; HttpOnly"
```

### `schemaRecord`

Schema transforming `Cookies` into a record of decoded string values keyed by
name.

```ts
import { Cookies } from "effect/unstable/http"

Cookies.schemaRecord // => Schema decoding Cookies → Record<string, string>
```

### `isCookie` / `isCookies` / `isEmpty` & `CookiesError`

`isCookie` / `isCookies` are runtime guards; `isEmpty` checks for no cookies;
`CookiesError` is the tagged error (`reason._tag` is `InvalidCookieName`,
`InvalidCookieValue`, `InvalidCookieDomain`, `InvalidCookiePath`, or
`CookieInfinityMaxAge`).

```ts
import { Cookies } from "effect/unstable/http"

Cookies.isCookies(Cookies.empty) // => true
Cookies.isEmpty(Cookies.empty) // => true
```