# Meili
An idiomatic, high-performance, and lightweight Meilisearch client for Elixir built on top of `Req` and `Finch`.
## Why Meili?
* **No Bloat**: Built directly on `Req`, leveraging connection pooling and HTTP/2 stream multiplexing out-of-the-box. No heavy middleware layers.
* **Idiomatic Elixir**:
* Provides both a global/default client configuration (via `Application` environment) and explicit client structs (for multi-tenant setups).
* Automatically converts Elixir-style `snake_case` keys to Meilisearch-style `camelCase` keys for query parameters, search options, and settings.
* Standard operations return `{:ok, result}` / `{:error, error}` and suffix-raising bang (`!`) functions (e.g., `search!/3`).
* Custom exception structures (`Meili.Error`) wrap Meilisearch-specific API errors (code, type, message, link).
* **Asynchronous Polling**: Includes a robust `wait_for_task/3` helper that polls tasks with backoff and raises clear error messages if tasks fail.
## Installation
Add `meili` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:meili, "~> 0.1.0"}
]
end
```
## Configuration
In your `config/config.exs` or `config/runtime.exs`:
```elixir
config :meili,
url: System.get_env("MEILI_HTTP_ADDR") || "http://localhost:7700",
key: System.get_env("MEILI_MASTER_KEY")
```
## Quick Start
### Global Client Mode
If you're using a single Meilisearch instance, configure your credentials in the config and call operations directly:
```elixir
# Create an index
{:ok, task} = Meili.create_index("movies", primary_key: "id")
# Block until index creation completes
Meili.wait_for_task!(task["taskUid"])
# Add documents
documents = [
%{id: 1, title: "Star Wars: Episode IV", genre: "Sci-Fi"},
%{id: 2, title: "The Dark Knight", genre: "Action"}
]
{:ok, task} = Meili.add_documents("movies", documents)
Meili.wait_for_task!(task["taskUid"])
# Search documents (note: snake_case parameters are automatically converted to camelCase)
{:ok, results} = Meili.search("movies", "star wars", limit: 5, show_ranking_score: true)
IO.inspect(results["hits"])
```
### Explicit Client Mode (Multi-tenant / Dynamic Setup)
If you need to connect to multiple instances dynamically:
```elixir
client = Meili.client(url: "https://my-meili-instance.com", key: "secret-key")
# Perform search with the explicit client
{:ok, results} = Meili.search(client, "movies", "batman")
```
### Settings Management
```elixir
settings = %{
searchable_attributes: ["title", "overview"],
filterable_attributes: ["genre"],
ranking_rules: ["words", "typo", "proximity"]
}
{:ok, task} = Meili.Settings.update("movies", settings)
Meili.wait_for_task!(task["taskUid"])
```
## Testing
This library comes with a mock-server testing suite built using `Bypass`. You can run the tests locally without needing a live Meilisearch instance:
```bash
mix test
```