# `PhoenixGenApi.ConfigPuller`
[🔗](https://github.com/ohhi-vn/phoenix_gen_api/blob/main/lib/phoenix_gen_api/config_cache/config_puller.ex#L1)

This module is responsible for periodically pulling function configurations (`%FunConfig{}`)
from remote nodes and updating the `ConfigDb`.

## Security

MFA safety validation is performed on remote modules before configs are stored.
When RPC verification fails (e.g., node unreachable), the config is **rejected** rather
than accepted — this is a more secure default than the previous behavior which allowed
configs when verification couldn't be completed.

The puller's behavior can be configured in your `config.exs` file:

```elixir
config :phoenix_gen_api, :gen_api,
  pull_timeout: 5_000,
  pull_interval: 30_000
```

- `pull_timeout`: The timeout for each RPC call in milliseconds (default: 5000).
- `pull_interval`: The interval between each pull operation in milliseconds (default: 30000).

## Version-Based Skip Mechanism

When a `ServiceConfig` has `version_module` and `version_function` configured, the puller
will first call the lightweight version check RPC before performing a full config pull.
If the returned version matches the locally stored version for that service, the full
pull is skipped — saving network bandwidth and reducing load on remote nodes.

The remote service should implement a version function that returns a value that changes
whenever the function configurations change. Good candidates include:

  - A monotonically increasing integer (e.g., `1`, `2`, `3`)
  - A semantic version string (e.g., `"1.2.3"`)
  - A content hash of the config data (e.g., `"a1b2c3d4"`)
  - A timestamp of the last config change (e.g., `"2024-01-15T10:30:00Z"`)

The version value is compared using strict equality (`==`), so any format that can be
compared this way will work.

If `version_module` or `version_function` is `nil`, version checking is disabled and
the full config pull will always be performed (backward compatible behavior).

Use `force_pull/0` to force a full pull regardless of version matching, which clears
all stored versions and re-fetches every service's configuration.

## Fault Tolerance

- Failed RPC calls are logged and do not crash the puller
- Node lists are validated before use
- Configuration validation prevents invalid configs from entering the cache
- Exponential backoff on repeated failures (up to a maximum)
- Version check failures fall back to full pull

## Security

- Validates that remote configs match the expected service name
- Validates FunConfig structs before adding to cache
- Rejects configs with invalid MFAs or node configurations

# `add`

Adds a list of services to the puller.
The `services` argument must be a list of `%ServiceConfig{}` structs.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `delete`

Deletes a list of services from the puller.
The `services` argument must be a list of `%ServiceConfig{}` structs.

Deleting a service also removes its stored version and API list entry.

# `force_pull`

Forces an immediate full pull of configurations from all registered services,
ignoring version checking. This clears all stored versions first, so every
service will be re-fetched regardless of whether its version has changed.

Use this when you want to guarantee a fresh configuration pull, for example
after a deployment or when you suspect the local cache is stale.

# `get_all_versions`

```elixir
@spec get_all_versions() :: %{required(String.t() | atom()) =&gt; term()}
```

Returns a map of all stored service versions.

The map keys are service names and the values are the stored version terms.
Services that were pulled without version checking configured will have `nil`
as their version value.

# `get_api_list`

Returns the list of APIs for a given service.

# `get_service_version`

```elixir
@spec get_service_version(String.t() | atom()) :: term() | nil
```

Returns the stored config version for a given service.

Returns `nil` if no version has been stored for the service. This indicates
that either the service hasn't been pulled yet, or version checking is not
configured for the service and no version was captured from a previous pull.

# `get_services`

Returns the map of services currently being pulled from.

# `pull`

Triggers an immediate pull of configurations from the registered services.
Version checking is respected — if the stored version matches the remote version,
the full pull for that service is skipped.

# `start_link`

Starts the `ConfigPuller` GenServer.

---

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