# `Combo.Inertia.Conn`
[🔗](https://github.com/combo-lab/combo_inertia/blob/v2.0.0/lib/combo/inertia/conn.ex#L1)

`%Plug.Conn{}` helpers for rendering Inertia responses.

# `always`

```elixir
@opaque always()
```

# `component`

```elixir
@type component() :: String.t()
```

# `deep_merge`

```elixir
@opaque deep_merge()
```

# `defer`

```elixir
@opaque defer()
```

# `merge`

```elixir
@opaque merge()
```

# `once`

```elixir
@opaque once()
```

# `optional`

```elixir
@opaque optional()
```

# `preserved_prop_key`

```elixir
@opaque preserved_prop_key()
```

# `prop_key`

```elixir
@type prop_key() :: raw_prop_key() | preserved_prop_key()
```

# `prop_value`

```elixir
@type prop_value() :: any()
```

# `props`

```elixir
@type props() :: map()
```

# `raw_prop_key`

```elixir
@type raw_prop_key() :: atom() | String.t()
```

# `render_opt`

```elixir
@type render_opt() :: {:ssr, boolean()}
```

# `render_opts`

```elixir
@type render_opts() :: [render_opt()]
```

# `scroll`

```elixir
@opaque scroll()
```

# `inertia_always`

```elixir
@spec inertia_always(value :: any()) :: always()
```

Marks a prop as "always", which means it will be always included in the props.

## Examples

    # ALWAYS included on standard visits
    # ALWAYS included on partial reloads
    # ALWAYS evaluated
    inertia_put_prop(conn, :users, inertia_always(Users.all()))

# `inertia_camelize_props`

```elixir
@spec inertia_camelize_props(Plug.Conn.t()) :: Plug.Conn.t()
```

Enable (or disable) automatic conversion of prop keys from snake case (e.g.
`inserted_at`), which is conventional in Elixir, to camel case (e.g.
`insertedAt`), which is conventional in JavaScript.

## Examples

Using `camelize_props` here will convert `first_name` to `firstName` in the
response props.

    conn
    |> inertia_put_prop(:first_name, "Bob")
    |> inertia_camelize_props()
    |> inertia_render("Home")

You may also pass a boolean to the `camelize_props` function (to override any
previously-set or globally-configured value):

    conn
    |> inertia_put_prop(:first_name, "Bob")
    |> inertia_camelize_props(false)
    |> inertia_render("Home")

# `inertia_camelize_props`

```elixir
@spec inertia_camelize_props(Plug.Conn.t(), boolean()) :: Plug.Conn.t()
```

# `inertia_clear_history`

```elixir
@spec inertia_clear_history(Plug.Conn.t()) :: Plug.Conn.t()
```

Instructs the client-side to clear the history state.

# `inertia_clear_history`

```elixir
@spec inertia_clear_history(Plug.Conn.t(), boolean()) :: Plug.Conn.t()
```

# `inertia_deep_merge`

```elixir
@spec inertia_deep_merge(prop_value()) :: deep_merge()
```

Marks a prop as "deep_merge", which means it will be deeply merged with
existing data on the client-side.

# `inertia_defer`

```elixir
@spec inertia_defer(fun()) :: defer()
```

Marks a prop as "defer", which means it will be loaded after initial page
render.

## Examples

    inertia_put_prop(conn, :users, inertia_defer(fn -> Users.all() end))
    inertia_put_prop(conn, :users, inertia_defer(fn -> Users.all() end, "group1"))

# `inertia_defer`

```elixir
@spec inertia_defer(fun(), String.t()) :: defer()
```

# `inertia_encrypt_history`

```elixir
@spec inertia_encrypt_history(Plug.Conn.t()) :: Plug.Conn.t()
```

Instructs the client-side to encrypt the current page's data before pushing
it to the history state.

# `inertia_encrypt_history`

```elixir
@spec inertia_encrypt_history(Plug.Conn.t(), boolean()) :: Plug.Conn.t()
```

# `inertia_force_redirect`

```elixir
@spec inertia_force_redirect(Plug.Conn.t(), opts :: keyword()) :: Plug.Conn.t()
```

Forces the Inertia client side to perform a redirect. This can be used as a
plug or inline when building a response.

This plug modifies the response to be a 409 Conflict response and include the
destination URL in the `x-inertia-location` header, which will cause the
Inertia client to perform a `window.location = url` visit.

**Note**: we automatically convert regular external redirects (via the Combo
`redirect` helper), but this function is useful if you need to force redirect
to a non-external route that is not handled by Inertia.

See https://inertiajs.com/redirects#external-redirects

## Examples

    conn
    |> inertia_force_redirect()
    |> redirect(to: "/non-inertia-powered-page")

# `inertia_merge`

```elixir
@spec inertia_merge(prop_value()) :: merge()
```

Marks a prop as "merge", which means it will be merged with existing data on
the client-side.

# `inertia_once`

```elixir
@spec inertia_once(
  fun_or_tagged :: fun() | defer() | merge() | deep_merge() | optional(),
  opts :: keyword()
) :: once()
```

Marks a prop as "once", which is cached on the client-side and reused on
subsequent pages that include the same prop.

## Options

- `:fresh` - When `true`, forces the prop to be refreshed ignoring the client cache.
  Also accepts a boolean condition. Defaults to `false`.
- `:until` - Sets an expiration time. Accepts a `DateTime` or integer seconds from now.
- `:as` - Custom key for sharing data across pages with different prop names.

## Examples

    # Basic once prop
    inertia_put_prop(conn, :plans, inertia_once(fn -> Plan.all() end))

    # Forcing a refresh
    inertia_put_prop(conn, :plans, inertia_once(fn -> Plan.all() end, fresh: true))

    # Expires in 1 hour
    inertia_put_prop(conn, :rates, inertia_once(
      fn -> ExchangeRate.current() end,
      until: 3600
    ))

    # Expires at a specific time
    inertia_put_prop(conn, :rates, inertia_once(
      fn -> ExchangeRate.current() end,
      until: DateTime.utc_now() |> DateTime.add(1, :day)
    ))

    # With custom key for sharing across pages
    inertia_put_prop(conn, :member_roles, inertia_once(fn -> Role.all() end, as: "roles"))

    # Combined options
    inertia_put_prop(conn, :plans, inertia_once(fn -> Plan.all() end,
      fresh: should_refresh?,
      until: DateTime.utc_now() |> DateTime.add(1, :day),
      as: "billing_plans"
    ))

    # Combining with other prop types
    inertia_put_prop(conn, :permissions, inertia_once(inertia_defer(fn -> Permission.all() end)))

# `inertia_optional`

```elixir
@spec inertia_optional(fun()) :: optional()
```

Marks a prop as "optional", which means it will only get evaluated when
explicitly requested in a partial reload.

## Examples

    // NEVER included on standard visits
    // OPTIONALLY included on partial reloads
    // ONLY evaluated when needed
    inertia_put_prop(conn, :users, inertia_optional(fn -> Users.all() end))

# `inertia_put_errors`

```elixir
@spec inertia_put_errors(Plug.Conn.t(), data :: term()) :: Plug.Conn.t()
```

Assigns errors to the Inertia page data. This helper accepts any data that
implements the `Combo.Inertia.Errors` protocol. By default, this library implements
error serializers for `Ecto.Changeset` and bare maps.

If you are serializing your own errors maps, they should take the following shape:

    %{
      "name" => "Name is required",
      "password" => "Password must be at least 5 characters",
      "team.name" => "Team name is required",
    }

When assigning a changeset, you may optionally pass a message-generating function
to use when traversing errors. See [`Ecto.Changeset.traverse_errors/2`](https://hexdocs.pm/ecto/Ecto.Changeset.html#traverse_errors/2)
for more information about the message function.

    defp default_msg_fun({msg, opts}) do
      Enum.reduce(opts, msg, fn {key, value}, acc ->
        String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end)
      end)
    end

This default implementation performs a simple string replacement for error
message containing variables, like `count`. For example, given the following
error:

    {"should be at least %{count} characters", [count: 3, validation: :length, min: 3]}

The generated description would be "should be at least 3 characters". If you would
prefer to use the `Gettext` module for pluralizing and localizing error messages, you
can override the message function:

    conn
    |> inertia_put_errors(changeset, fn {msg, opts} ->
      if count = opts[:count] do
        Gettext.dngettext(MyApp.Web.Gettext, "errors", msg, msg, count, opts)
      else
        Gettext.dgettext(MyApp.Web.Gettext, "errors", msg, opts)
      end
    end)

# `inertia_put_errors`

```elixir
@spec inertia_put_errors(Plug.Conn.t(), data :: term(), msg_fun :: function()) ::
  Plug.Conn.t()
```

# `inertia_put_prop`

```elixir
@spec inertia_put_prop(Plug.Conn.t(), prop_key(), prop_value()) :: Plug.Conn.t()
```

Puts a prop to the Inertia page data.

## Examples

    # ALWAYS included on standard visits
    # OPTIONALLY included on partial reloads
    # ALWAYS evaluated
    inertia_put_prop(conn, :users, Users.all())

    # ALWAYS included on standard visits
    # OPTIONALLY included on partial reloads
    # ONLY evaluated when needed
    inertia_put_prop(conn, :users, fn -> Users.all() end)

# `inertia_render`

```elixir
@spec inertia_render(Plug.Conn.t(), component()) :: Plug.Conn.t()
```

Renders an Inertia response.

## Options

- `ssr`: whether to server-side render the response (see the docs on
  "Server-side rendering" in the README for more information on setting this
  up). Defaults to the globally-configured value, or `false` if no global
  config is specified.

## Examples

    conn
    |> inertia_put_prop(:user_id, 1)
    |> inertia_render("SettingsPage")

You may pass additional props as map for the third argument:

    conn
    |> inertia_put_prop(:user_id, 1)
    |> inertia_render("SettingsPage", %{name: "Bob"})

You may also pass options for the last positional argument:

    conn
    |> inertia_put_prop(:user_id, 1)
    |> inertia_render("SettingsPage", ssr: true)

    conn
    |> inertia_put_prop(:user_id, 1)
    |> inertia_render("SettingsPage", %{name: "Bob"}, ssr: true)

# `inertia_render`

```elixir
@spec inertia_render(Plug.Conn.t(), component(), props() | render_opts()) ::
  Plug.Conn.t()
```

# `inertia_render`

```elixir
@spec inertia_render(Plug.Conn.t(), component(), props(), render_opts()) ::
  Plug.Conn.t()
```

# `inertia_response?`

```elixir
@spec inertia_response?(Plug.Conn.t()) :: boolean()
```

Determines if a response has been rendered with Inertia.

# `inertia_scroll`

```elixir
@spec inertia_scroll(value :: any(), opts :: keyword()) :: scroll()
```

Marks a prop for infinite scroll pagination. It automatically configures
merge behavior for the data key and extracts pagination metadata for the
client side `<InfiniteScroll>` component.

## Options

- `:wrapper` - The key containing the data items. Default to `"data"`.
- `:page_name` - Override the page query parameter name.
- `:metadata` - Custom metadata extraction function.

## Examples

    # Basic usage with auto-detected metadata
    inertia_put_prop(conn, :users, inertia_scroll(paginated_users))

    # With lazy evaluation
    inertia_put_prop(conn, :users, inertia_scroll(fn -> Users.paginate(opts) end))

    # Custom wrapper key
    inertia_put_prop(conn, :users, inertia_scroll(data, wrapper: "items"))

    # Custom metadata
    inertia_put_prop(conn, :users, inertia_scroll(data, metadata: fn data ->
      %{page_name: "p", current_page: 1, next_page: 2, previous_page: nil}
    end))

# `preserve_case`

```elixir
@spec preserve_case(raw_prop_key()) :: preserved_prop_key()
```

Prevents auto-transformation of a prop key to camel-case (when
`camelize_props` is enabled).

## Example

    conn
    |> inertia_put_prop(preserve_case(:this_will_not_be_camelized), "value")
    |> inertia_put_prop(:this_will_be_camelized, "another_value")
    |> inertia_camelize_props()
    |> inertia_render("Home")

You can also use this helper inside of nested props:

    conn
    |> inertia_put_prop(:user, %{
      preserve_case(:this_will_not_be_camelized) => "value",
      this_will_be_camelized: "another_value"
    })
    |> inertia_camelize_props()
    |> inertia_render("Home")

---

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