# `Sofa.Changes.Query`
[🔗](https://github.com/skunkwerks/sofa/blob/0.5.1/lib/sofa/changes/query.ex#L1)

Turns follower options into the request for `GET|POST /{db}/_changes`.

The CouchDB changes API takes its parameters in the query string, with two
exceptions that need a `POST` and a JSON body: the `_doc_ids` and
`_selector` filters. This module encodes the documented parameters the way
CouchDB expects them, see
https://docs.couchdb.org/en/stable/api/database/changes.html

## Options

Every option is sent as the query parameter of the same name, encoded as:

- booleans, integers and atoms become their string form (`true`, `5`,
  `all_docs`, `now`)
- strings are sent verbatim, so `style: "all_docs"` works as well
- `nil` drops the parameter, so `heartbeat: nil` lets the server side
  `timeout` end the response. Dropping both is refused, as nothing would
  ever end a quiet feed and a dead connection would go unnoticed

A few options are special:

- `:feed` defaults to `:continuous`. `:longpoll` and `:normal` are the
  alternatives
- `longpoll: true` is shorthand for `feed: :longpoll`
- `:heartbeat` defaults to 60 000 ms and `:timeout` to 7 200 000 ms
- `:since` is kept on the struct so the follower can advance it between
  reconnects. It is sent verbatim and never parsed. Only `:now` or an
  opaque sequence string are accepted. Anything else raises an
  `ArgumentError`. Omitting it leaves `since` as `nil` and drops the
  parameter, which begins at the start of the feed
- `doc_ids: [...]` switches to `POST` with `filter=_doc_ids` and the ids in
  the body, overriding any `:filter`
- `selector: %{...}` does the same with `filter=_selector`
- `query_params: [k: v]` adds raw parameters, as used for filter function
  arguments

## Examples

    iex> Sofa.Changes.Query.new([]) |> Sofa.Changes.Query.params()
    [{"feed", "continuous"}, {"heartbeat", "60000"}, {"timeout", "7200000"}]

    iex> Sofa.Changes.Query.new(longpoll: true, since: "42", heartbeat: nil)
    ...> |> Sofa.Changes.Query.params()
    [{"feed", "longpoll"}, {"timeout", "7200000"}, {"since", "42"}]

    iex> %Sofa.Changes.Query{method: :post, body: body} =
    ...>   Sofa.Changes.Query.new(doc_ids: ["a", "b"], filter: "ignored")
    iex> body
    %{"doc_ids" => ["a", "b"]}

# `feed`

```elixir
@type feed() :: :continuous | :longpoll | :normal
```

# `since`

```elixir
@type since() :: String.t() | :now | nil
```

# `t`

```elixir
@type t() :: %Sofa.Changes.Query{
  body: map() | nil,
  feed: feed(),
  heartbeat: pos_integer() | nil,
  method: :get | :post,
  params: [{String.t(), String.t()}],
  since: since(),
  timeout: pos_integer() | nil
}
```

# `include_docs?`

```elixir
@spec include_docs?(t()) :: boolean()
```

Whether rows will carry the document under `"doc"`.

## Examples

    iex> Sofa.Changes.Query.new(include_docs: true) |> Sofa.Changes.Query.include_docs?()
    true
    iex> Sofa.Changes.Query.new([]) |> Sofa.Changes.Query.include_docs?()
    false

# `new`

```elixir
@spec new(keyword()) :: t()
```

Builds a query from follower options, see the module documentation.

Raises `ArgumentError` on an unknown `:feed`, or when both `:heartbeat`
and `:timeout` are `nil`.

# `params`

```elixir
@spec params(t()) :: [{String.t(), String.t()}]
```

The query string parameters for the next request, with `since` included
when known. The order is stable: `feed`, then the remaining options in
the order given, then `since`.

# `since`

```elixir
@spec since(t(), since()) :: t()
```

Advances `since`, as after a delivered row or a `last_seq`.

---

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