Skip to main content

lib/meili.ex

defmodule Meili do
  @moduledoc """
  An idiomatic, high-performance Meilisearch client for Elixir.
  """

  alias Meili.Client

  @doc """
  Builds a custom `Meili.Client` instance with the given options.

  ## Options
    * `:url` or `:endpoint` - The Meilisearch base URL (default: `http://localhost:7700`).
    * `:key` or `:api_key` - The API key or master key for authentication.
    * `:req_options` - Custom configuration options passed directly to `Req.new/1`.

  ## Examples
      client = Meili.client(url: "http://localhost:7700", key: "secret-key")
  """
  @spec client(Keyword.t() | map()) :: Client.t()
  def client(opts \\ []) do
    Client.new(opts)
  end

  @doc """
  Builds a `Meili.Client` from the global application environment.

  ## Examples
      client = Meili.default_client()
  """
  @spec default_client() :: Client.t()
  def default_client do
    config = Application.get_all_env(:meili)
    Client.new(config)
  end

  # --- Search Delegates ---

  @spec search(
          Client.t() | String.t(),
          String.t() | map(),
          String.t() | Keyword.t() | map(),
          Keyword.t()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate search(client_or_uid, uid_or_query, query_or_opts \\ [], opts \\ []),
    to: Meili.Search

  @spec search!(
          Client.t() | String.t(),
          String.t() | map(),
          String.t() | Keyword.t() | map(),
          Keyword.t()
        ) :: map() | no_return()
  defdelegate search!(client_or_uid, uid_or_query, query_or_opts \\ [], opts \\ []),
    to: Meili.Search

  @spec search_similar(
          Client.t() | String.t(),
          String.t() | map(),
          map() | Keyword.t(),
          Keyword.t()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate search_similar(client_or_uid, uid_or_body, body_or_opts \\ [], opts \\ []),
    to: Meili.Search,
    as: :similar

  @spec search_similar!(
          Client.t() | String.t(),
          String.t() | map(),
          map() | Keyword.t(),
          Keyword.t()
        ) :: map() | no_return()
  defdelegate search_similar!(client_or_uid, uid_or_body, body_or_opts \\ [], opts \\ []),
    to: Meili.Search,
    as: :similar!

  @spec facet_search(
          Client.t() | String.t(),
          String.t(),
          String.t() | nil,
          String.t() | Keyword.t(),
          Keyword.t()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate facet_search(
                client_or_uid,
                uid_or_facet_name,
                facet_name_or_query \\ nil,
                facet_query_or_opts \\ [],
                opts \\ []
              ),
              to: Meili.Search

  @spec facet_search!(
          Client.t() | String.t(),
          String.t(),
          String.t() | nil,
          String.t() | Keyword.t(),
          Keyword.t()
        ) :: map() | no_return()
  defdelegate facet_search!(
                client_or_uid,
                uid_or_facet_name,
                facet_name_or_query \\ nil,
                facet_query_or_opts \\ [],
                opts \\ []
              ),
              to: Meili.Search

  @spec multi_search(Client.t() | list(map()), list(map()) | Keyword.t(), Keyword.t()) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate multi_search(client_or_queries, queries_or_opts \\ [], opts \\ []), to: Meili.Search

  @spec multi_search!(Client.t() | list(map()), list(map()) | Keyword.t(), Keyword.t()) ::
          map() | no_return()
  defdelegate multi_search!(client_or_queries, queries_or_opts \\ [], opts \\ []),
    to: Meili.Search

  # --- Document Delegates ---

  @spec list_documents(
          Client.t() | String.t(),
          String.t() | Keyword.t(),
          Keyword.t()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate list_documents(client_or_uid, uid_or_opts \\ [], opts \\ []),
    to: Meili.Document,
    as: :list

  @spec list_documents!(
          Client.t() | String.t(),
          String.t() | Keyword.t(),
          Keyword.t()
        ) :: map() | no_return()
  defdelegate list_documents!(client_or_uid, uid_or_opts \\ [], opts \\ []),
    to: Meili.Document,
    as: :list!

  @spec get_document(
          Client.t() | String.t(),
          String.t() | integer(),
          String.t() | integer() | Keyword.t(),
          Keyword.t()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate get_document(client_or_uid, uid_or_doc_id, doc_id_or_opts \\ [], opts \\ []),
    to: Meili.Document,
    as: :get

  @spec get_document!(
          Client.t() | String.t(),
          String.t() | integer(),
          String.t() | integer() | Keyword.t(),
          Keyword.t()
        ) :: map() | no_return()
  defdelegate get_document!(client_or_uid, uid_or_doc_id, doc_id_or_opts \\ [], opts \\ []),
    to: Meili.Document,
    as: :get!

  @spec add_documents(
          Client.t() | String.t(),
          String.t() | list(map()),
          list(map()) | Keyword.t(),
          Keyword.t()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate add_documents(client_or_uid, uid_or_documents, documents_or_opts \\ [], opts \\ []),
    to: Meili.Document,
    as: :add_or_replace

  @spec add_documents!(
          Client.t() | String.t(),
          String.t() | list(map()),
          list(map()) | Keyword.t(),
          Keyword.t()
        ) :: map() | no_return()
  defdelegate add_documents!(
                client_or_uid,
                uid_or_documents,
                documents_or_opts \\ [],
                opts \\ []
              ), to: Meili.Document, as: :add_or_replace!

  @spec update_documents(
          Client.t() | String.t(),
          String.t() | list(map()),
          list(map()) | Keyword.t(),
          Keyword.t()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate update_documents(
                client_or_uid,
                uid_or_documents,
                documents_or_opts \\ [],
                opts \\ []
              ), to: Meili.Document, as: :add_or_update

  @spec update_documents!(
          Client.t() | String.t(),
          String.t() | list(map()),
          list(map()) | Keyword.t(),
          Keyword.t()
        ) :: map() | no_return()
  defdelegate update_documents!(
                client_or_uid,
                uid_or_documents,
                documents_or_opts \\ [],
                opts \\ []
              ), to: Meili.Document, as: :add_or_update!

  @spec delete_document(
          Client.t() | String.t(),
          String.t() | integer(),
          String.t() | integer() | nil
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate delete_document(client_or_uid, uid_or_doc_id, doc_id_or_nil \\ nil),
    to: Meili.Document,
    as: :delete

  @spec delete_document!(
          Client.t() | String.t(),
          String.t() | integer(),
          String.t() | integer() | nil
        ) :: map() | no_return()
  defdelegate delete_document!(client_or_uid, uid_or_doc_id, doc_id_or_nil \\ nil),
    to: Meili.Document,
    as: :delete!

  @spec delete_documents_batch(
          Client.t() | String.t(),
          String.t() | list(),
          list() | nil
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate delete_documents_batch(client_or_uid, uid_or_doc_ids, doc_ids_or_nil \\ nil),
    to: Meili.Document,
    as: :delete_batch

  @spec delete_documents_batch!(
          Client.t() | String.t(),
          String.t() | list(),
          list() | nil
        ) :: map() | no_return()
  defdelegate delete_documents_batch!(client_or_uid, uid_or_doc_ids, doc_ids_or_nil \\ nil),
    to: Meili.Document,
    as: :delete_batch!

  @spec delete_all_documents(Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate delete_all_documents(client_or_uid, uid_or_nil \\ nil),
    to: Meili.Document,
    as: :delete_all

  @spec delete_all_documents!(Client.t() | String.t(), String.t() | nil) :: map() | no_return()
  defdelegate delete_all_documents!(client_or_uid, uid_or_nil \\ nil),
    to: Meili.Document,
    as: :delete_all!

  @spec edit_documents(
          Client.t() | String.t(),
          String.t() | map(),
          map() | Keyword.t(),
          Keyword.t()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate edit_documents(client_or_uid, uid_or_queries, queries_or_opts \\ [], opts \\ []),
    to: Meili.Document,
    as: :edit

  @spec edit_documents!(
          Client.t() | String.t(),
          String.t() | map(),
          map() | Keyword.t(),
          Keyword.t()
        ) :: map() | no_return()
  defdelegate edit_documents!(client_or_uid, uid_or_queries, queries_or_opts \\ [], opts \\ []),
    to: Meili.Document,
    as: :edit!

  @spec stream_documents(
          Client.t() | String.t(),
          String.t() | Keyword.t(),
          Keyword.t()
        ) :: Enumerable.t()
  defdelegate stream_documents(client_or_uid, uid_or_opts \\ [], opts \\ []),
    to: Meili.Document,
    as: :stream

  # --- Index Delegates ---

  @spec create_index(
          Client.t() | String.t(),
          String.t() | Keyword.t() | map(),
          Keyword.t() | map()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate create_index(client_or_uid, uid_or_opts \\ [], opts \\ []),
    to: Meili.Index,
    as: :create

  @spec create_index!(
          Client.t() | String.t(),
          String.t() | Keyword.t() | map(),
          Keyword.t() | map()
        ) :: map() | no_return()
  defdelegate create_index!(client_or_uid, uid_or_opts \\ [], opts \\ []),
    to: Meili.Index,
    as: :create!

  @spec update_index(
          Client.t() | String.t(),
          String.t() | Keyword.t() | map(),
          Keyword.t() | map()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate update_index(client_or_uid, uid_or_opts \\ [], opts \\ []),
    to: Meili.Index,
    as: :update

  @spec update_index!(
          Client.t() | String.t(),
          String.t() | Keyword.t() | map(),
          Keyword.t() | map()
        ) :: map() | no_return()
  defdelegate update_index!(client_or_uid, uid_or_opts \\ [], opts \\ []),
    to: Meili.Index,
    as: :update!

  @spec delete_index(Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate delete_index(client_or_uid, uid_or_nil \\ nil), to: Meili.Index, as: :delete

  @spec delete_index!(Client.t() | String.t(), String.t() | nil) :: map() | no_return()
  defdelegate delete_index!(client_or_uid, uid_or_nil \\ nil), to: Meili.Index, as: :delete!

  @spec get_index(Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate get_index(client_or_uid, uid_or_nil \\ nil), to: Meili.Index, as: :get

  @spec get_index!(Client.t() | String.t(), String.t() | nil) :: map() | no_return()
  defdelegate get_index!(client_or_uid, uid_or_nil \\ nil), to: Meili.Index, as: :get!

  @spec list_indexes(Client.t() | Keyword.t() | nil, Keyword.t()) ::
          {:ok, list() | map()} | {:error, Meili.Error.t()}
  defdelegate list_indexes(client_or_opts \\ nil, opts \\ []), to: Meili.Index, as: :list

  @spec list_indexes!(Client.t() | Keyword.t() | nil, Keyword.t()) ::
          list() | map() | no_return()
  defdelegate list_indexes!(client_or_opts \\ nil, opts \\ []), to: Meili.Index, as: :list!

  @spec stream_indexes(Client.t() | Keyword.t(), Keyword.t()) :: Enumerable.t()
  defdelegate stream_indexes(client_or_opts \\ [], opts \\ []), to: Meili.Index, as: :stream

  @spec swap_indexes(Client.t() | list(map()), list(map()) | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate swap_indexes(client_or_indexes, indexes_or_nil \\ nil),
    to: Meili.Index,
    as: :swap

  @spec swap_indexes!(Client.t() | list(map()), list(map()) | nil) :: map() | no_return()
  defdelegate swap_indexes!(client_or_indexes, indexes_or_nil \\ nil),
    to: Meili.Index,
    as: :swap!

  @spec index_stats(Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate index_stats(client_or_uid, uid_or_nil \\ nil), to: Meili.Index, as: :stats

  @spec index_stats!(Client.t() | String.t(), String.t() | nil) :: map() | no_return()
  defdelegate index_stats!(client_or_uid, uid_or_nil \\ nil), to: Meili.Index, as: :stats!

  @spec compact_index(Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate compact_index(client_or_uid, uid_or_nil \\ nil), to: Meili.Index, as: :compact

  @spec compact_index!(Client.t() | String.t(), String.t() | nil) :: map() | no_return()
  defdelegate compact_index!(client_or_uid, uid_or_nil \\ nil), to: Meili.Index, as: :compact!

  # --- Health Delegates ---

  @spec health(Client.t() | nil) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate health(client \\ nil), to: Meili.Health

  @spec health!(Client.t() | nil) :: map() | no_return()
  defdelegate health!(client \\ nil), to: Meili.Health

  @spec version(Client.t() | nil) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate version(client \\ nil), to: Meili.Health

  @spec version!(Client.t() | nil) :: map() | no_return()
  defdelegate version!(client \\ nil), to: Meili.Health

  @spec stats(Client.t() | nil) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate stats(client \\ nil), to: Meili.Health

  @spec stats!(Client.t() | nil) :: map() | no_return()
  defdelegate stats!(client \\ nil), to: Meili.Health

  # --- Task Delegates ---

  @spec wait_for_task(
          Client.t() | integer() | String.t(),
          integer() | String.t() | Keyword.t(),
          Keyword.t()
        ) :: {:ok, map()} | {:error, :timeout | Meili.Error.t()}
  defdelegate wait_for_task(client_or_uid, uid_or_opts \\ [], opts \\ []), to: Meili.Task

  @spec wait_for_task!(
          Client.t() | integer() | String.t(),
          integer() | String.t() | Keyword.t(),
          Keyword.t()
        ) :: map() | no_return()
  defdelegate wait_for_task!(client_or_uid, uid_or_opts \\ [], opts \\ []), to: Meili.Task

  @spec wait_for_tasks(
          Client.t() | Keyword.t() | nil,
          Keyword.t()
        ) :: {:ok, :completed} | {:error, :timeout | Meili.Error.t()}
  defdelegate wait_for_tasks(client_or_opts \\ nil, opts \\ []), to: Meili.Task

  @spec wait_for_tasks!(
          Client.t() | Keyword.t() | nil,
          Keyword.t()
        ) :: :completed | no_return()
  defdelegate wait_for_tasks!(client_or_opts \\ nil, opts \\ []), to: Meili.Task

  @spec list_tasks(Client.t() | Keyword.t() | nil, Keyword.t()) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate list_tasks(client_or_opts \\ nil, opts \\ []), to: Meili.Task, as: :list

  @spec list_tasks!(Client.t() | Keyword.t() | nil, Keyword.t()) :: map() | no_return()
  defdelegate list_tasks!(client_or_opts \\ nil, opts \\ []), to: Meili.Task, as: :list!

  @spec get_task(Client.t() | integer() | String.t(), integer() | String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate get_task(client_or_uid, uid_or_nil \\ nil), to: Meili.Task, as: :get

  @spec get_task!(Client.t() | integer() | String.t(), integer() | String.t() | nil) ::
          map() | no_return()
  defdelegate get_task!(client_or_uid, uid_or_nil \\ nil), to: Meili.Task, as: :get!

  @spec cancel_tasks(Client.t() | Keyword.t() | nil, Keyword.t()) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate cancel_tasks(client_or_opts \\ nil, opts \\ []), to: Meili.Task, as: :cancel

  @spec cancel_tasks!(Client.t() | Keyword.t() | nil, Keyword.t()) :: map() | no_return()
  defdelegate cancel_tasks!(client_or_opts \\ nil, opts \\ []), to: Meili.Task, as: :cancel!

  @spec delete_tasks(Client.t() | Keyword.t() | nil, Keyword.t()) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate delete_tasks(client_or_opts \\ nil, opts \\ []), to: Meili.Task, as: :delete

  @spec delete_tasks!(Client.t() | Keyword.t() | nil, Keyword.t()) :: map() | no_return()
  defdelegate delete_tasks!(client_or_opts \\ nil, opts \\ []), to: Meili.Task, as: :delete!

  # --- Token Delegates ---

  @spec generate_tenant_token(String.t() | nil, map() | nil, String.t() | nil, Keyword.t()) ::
          {:ok, String.t()} | {:error, :missing_secret | :invalid_uid | :missing_search_rules}
  defdelegate generate_tenant_token(api_key_uid, search_rules, secret \\ nil, opts \\ []),
    to: Meili.Token,
    as: :generate

  @spec generate_tenant_token!(String.t() | nil, map() | nil, String.t() | nil, Keyword.t()) ::
          String.t() | no_return()
  defdelegate generate_tenant_token!(api_key_uid, search_rules, secret \\ nil, opts \\ []),
    to: Meili.Token,
    as: :generate!

  # --- Dump Delegates ---

  @spec create_dump(Client.t() | nil) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate create_dump(client \\ nil), to: Meili.Dump, as: :create

  @spec create_dump!(Client.t() | nil) :: map() | no_return()
  defdelegate create_dump!(client \\ nil), to: Meili.Dump, as: :create!

  # --- Batch Delegates ---

  @spec get_batch(Client.t() | integer() | String.t(), integer() | String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate get_batch(client_or_uid, uid_or_nil \\ nil), to: Meili.Batch, as: :get

  @spec get_batch!(Client.t() | integer() | String.t(), integer() | String.t() | nil) ::
          map() | no_return()
  defdelegate get_batch!(client_or_uid, uid_or_nil \\ nil), to: Meili.Batch, as: :get!

  @spec list_batches(Client.t() | Keyword.t() | nil, Keyword.t()) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate list_batches(client_or_opts \\ nil, opts \\ []), to: Meili.Batch, as: :list

  @spec list_batches!(Client.t() | Keyword.t() | nil, Keyword.t()) :: map() | no_return()
  defdelegate list_batches!(client_or_opts \\ nil, opts \\ []), to: Meili.Batch, as: :list!

  # --- Chat Delegates ---

  @spec chat_completions(
          Client.t() | String.t(),
          String.t() | map() | nil,
          map() | Keyword.t(),
          Keyword.t()
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate chat_completions(
                client_or_workspace,
                workspace_or_body \\ nil,
                body_or_opts \\ [],
                opts \\ []
              ),
              to: Meili.Chat,
              as: :completions

  @spec chat_completions!(
          Client.t() | String.t(),
          String.t() | map() | nil,
          map() | Keyword.t(),
          Keyword.t()
        ) :: map() | no_return()
  defdelegate chat_completions!(
                client_or_workspace,
                workspace_or_body \\ nil,
                body_or_opts \\ [],
                opts \\ []
              ),
              to: Meili.Chat,
              as: :completions!

  @spec get_chat_settings(Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate get_chat_settings(client_or_workspace, workspace_or_nil \\ nil),
    to: Meili.Chat,
    as: :get_settings

  @spec get_chat_settings!(Client.t() | String.t(), String.t() | nil) :: map() | no_return()
  defdelegate get_chat_settings!(client_or_workspace, workspace_or_nil \\ nil),
    to: Meili.Chat,
    as: :get_settings!

  @spec update_chat_settings(
          Client.t() | String.t(),
          String.t() | map() | Keyword.t() | nil,
          map() | Keyword.t() | nil
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate update_chat_settings(
                client_or_workspace,
                workspace_or_settings \\ nil,
                settings_or_nil \\ nil
              ),
              to: Meili.Chat,
              as: :update_settings

  @spec update_chat_settings!(
          Client.t() | String.t(),
          String.t() | map() | Keyword.t() | nil,
          map() | Keyword.t() | nil
        ) :: map() | no_return()
  defdelegate update_chat_settings!(
                client_or_workspace,
                workspace_or_settings \\ nil,
                settings_or_nil \\ nil
              ),
              to: Meili.Chat,
              as: :update_settings!

  @spec reset_chat_settings(Client.t() | String.t(), String.t() | nil) ::
          {:ok, term()} | {:error, Meili.Error.t()}
  defdelegate reset_chat_settings(client_or_workspace, workspace_or_nil \\ nil),
    to: Meili.Chat,
    as: :reset_settings

  @spec reset_chat_settings!(Client.t() | String.t(), String.t() | nil) :: term() | no_return()
  defdelegate reset_chat_settings!(client_or_workspace, workspace_or_nil \\ nil),
    to: Meili.Chat,
    as: :reset_settings!

  # --- Snapshot Delegates ---

  @spec create_snapshot(Client.t() | nil) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate create_snapshot(client \\ nil), to: Meili.Snapshot, as: :create

  @spec create_snapshot!(Client.t() | nil) :: map() | no_return()
  defdelegate create_snapshot!(client \\ nil), to: Meili.Snapshot, as: :create!

  # --- Dynamic Search Rules Delegates ---

  @spec list_dynamic_search_rules(Client.t() | Keyword.t() | nil, Keyword.t()) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate list_dynamic_search_rules(client_or_opts \\ nil, opts \\ []),
    to: Meili.DynamicSearchRules,
    as: :list

  @spec list_dynamic_search_rules!(Client.t() | Keyword.t() | nil, Keyword.t()) ::
          map() | no_return()
  defdelegate list_dynamic_search_rules!(client_or_opts \\ nil, opts \\ []),
    to: Meili.DynamicSearchRules,
    as: :list!

  @spec get_dynamic_search_rule(Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate get_dynamic_search_rule(client_or_uid, uid_or_nil \\ nil),
    to: Meili.DynamicSearchRules,
    as: :get

  @spec get_dynamic_search_rule!(Client.t() | String.t(), String.t() | nil) ::
          map() | no_return()
  defdelegate get_dynamic_search_rule!(client_or_uid, uid_or_nil \\ nil),
    to: Meili.DynamicSearchRules,
    as: :get!

  @spec update_dynamic_search_rule(
          Client.t() | String.t(),
          String.t() | map() | Keyword.t() | nil,
          map() | Keyword.t() | nil
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate update_dynamic_search_rule(
                client_or_uid,
                uid_or_params \\ nil,
                params_or_nil \\ nil
              ),
              to: Meili.DynamicSearchRules,
              as: :update

  @spec update_dynamic_search_rule!(
          Client.t() | String.t(),
          String.t() | map() | Keyword.t() | nil,
          map() | Keyword.t() | nil
        ) :: map() | no_return()
  defdelegate update_dynamic_search_rule!(
                client_or_uid,
                uid_or_params \\ nil,
                params_or_nil \\ nil
              ),
              to: Meili.DynamicSearchRules,
              as: :update!

  @spec delete_dynamic_search_rule(Client.t() | String.t(), String.t() | nil) ::
          {:ok, term()} | {:error, Meili.Error.t()}
  defdelegate delete_dynamic_search_rule(client_or_uid, uid_or_nil \\ nil),
    to: Meili.DynamicSearchRules,
    as: :delete

  @spec delete_dynamic_search_rule!(Client.t() | String.t(), String.t() | nil) ::
          term() | no_return()
  defdelegate delete_dynamic_search_rule!(client_or_uid, uid_or_nil \\ nil),
    to: Meili.DynamicSearchRules,
    as: :delete!

  # --- Export Delegates ---

  @spec create_export(Client.t() | map() | Keyword.t(), map() | Keyword.t(), Keyword.t()) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate create_export(client_or_params, params_or_opts \\ [], opts \\ []),
    to: Meili.Export,
    as: :create

  @spec create_export!(Client.t() | map() | Keyword.t(), map() | Keyword.t(), Keyword.t()) ::
          map() | no_return()
  defdelegate create_export!(client_or_params, params_or_opts \\ [], opts \\ []),
    to: Meili.Export,
    as: :create!

  # --- Webhook Delegates ---

  @spec list_webhooks(Client.t() | nil) :: {:ok, term()} | {:error, Meili.Error.t()}
  defdelegate list_webhooks(client \\ nil), to: Meili.Webhook, as: :list

  @spec list_webhooks!(Client.t() | nil) :: term() | no_return()
  defdelegate list_webhooks!(client \\ nil), to: Meili.Webhook, as: :list!

  @spec get_webhook(Client.t() | String.t(), String.t() | nil) ::
          {:ok, term()} | {:error, Meili.Error.t()}
  defdelegate get_webhook(client_or_uuid, uuid_or_nil \\ nil), to: Meili.Webhook, as: :get

  @spec get_webhook!(Client.t() | String.t(), String.t() | nil) :: term() | no_return()
  defdelegate get_webhook!(client_or_uuid, uuid_or_nil \\ nil), to: Meili.Webhook, as: :get!

  @spec create_webhook(Client.t() | map() | list(), map() | list() | nil) ::
          {:ok, term()} | {:error, Meili.Error.t()}
  defdelegate create_webhook(client_or_params, params_or_nil \\ nil),
    to: Meili.Webhook,
    as: :create

  @spec create_webhook!(Client.t() | map() | list(), map() | list() | nil) :: term() | no_return()
  defdelegate create_webhook!(client_or_params, params_or_nil \\ nil),
    to: Meili.Webhook,
    as: :create!

  @spec update_webhook(
          Client.t() | String.t(),
          String.t() | map() | list() | nil,
          map() | list() | nil
        ) :: {:ok, term()} | {:error, Meili.Error.t()}
  defdelegate update_webhook(client_or_uuid, uuid_or_params \\ nil, params_or_nil \\ nil),
    to: Meili.Webhook,
    as: :update

  @spec update_webhook!(
          Client.t() | String.t(),
          String.t() | map() | list() | nil,
          map() | list() | nil
        ) :: term() | no_return()
  defdelegate update_webhook!(client_or_uuid, uuid_or_params \\ nil, params_or_nil \\ nil),
    to: Meili.Webhook,
    as: :update!

  @spec delete_webhook(Client.t() | String.t(), String.t() | nil) ::
          {:ok, term()} | {:error, Meili.Error.t()}
  defdelegate delete_webhook(client_or_uuid, uuid_or_nil \\ nil), to: Meili.Webhook, as: :delete

  @spec delete_webhook!(Client.t() | String.t(), String.t() | nil) :: term() | no_return()
  defdelegate delete_webhook!(client_or_uuid, uuid_or_nil \\ nil), to: Meili.Webhook, as: :delete!

  # --- Key Delegates ---

  @spec list_keys(Client.t() | Keyword.t() | nil, Keyword.t()) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate list_keys(client_or_opts \\ nil, opts \\ []), to: Meili.Key, as: :list

  @spec list_keys!(Client.t() | Keyword.t() | nil, Keyword.t()) :: map() | no_return()
  defdelegate list_keys!(client_or_opts \\ nil, opts \\ []), to: Meili.Key, as: :list!

  @spec get_key(Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate get_key(client_or_key, key_or_nil \\ nil), to: Meili.Key, as: :get

  @spec get_key!(Client.t() | String.t(), String.t() | nil) :: map() | no_return()
  defdelegate get_key!(client_or_key, key_or_nil \\ nil), to: Meili.Key, as: :get!

  @spec create_key(Client.t() | map() | Keyword.t(), map() | Keyword.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate create_key(client_or_params, params_or_nil \\ nil), to: Meili.Key, as: :create

  @spec create_key!(Client.t() | map() | Keyword.t(), map() | Keyword.t() | nil) ::
          map() | no_return()
  defdelegate create_key!(client_or_params, params_or_nil \\ nil), to: Meili.Key, as: :create!

  @spec update_key(
          Client.t() | String.t(),
          String.t() | map() | Keyword.t() | nil,
          map() | Keyword.t() | nil
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate update_key(client_or_key, key_or_params \\ nil, params_or_nil \\ nil),
    to: Meili.Key,
    as: :update

  @spec update_key!(
          Client.t() | String.t(),
          String.t() | map() | Keyword.t() | nil,
          map() | Keyword.t() | nil
        ) :: map() | no_return()
  defdelegate update_key!(client_or_key, key_or_params \\ nil, params_or_nil \\ nil),
    to: Meili.Key,
    as: :update!

  @spec delete_key(Client.t() | String.t(), String.t() | nil) ::
          {:ok, term()} | {:error, Meili.Error.t()}
  defdelegate delete_key(client_or_key, key_or_nil \\ nil), to: Meili.Key, as: :delete

  @spec delete_key!(Client.t() | String.t(), String.t() | nil) :: term() | no_return()
  defdelegate delete_key!(client_or_key, key_or_nil \\ nil), to: Meili.Key, as: :delete!

  # --- Settings Delegates ---

  @spec get_settings(Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate get_settings(client_or_uid, uid_or_nil \\ nil), to: Meili.Settings, as: :get

  @spec get_settings!(Client.t() | String.t(), String.t() | nil) :: map() | no_return()
  defdelegate get_settings!(client_or_uid, uid_or_nil \\ nil), to: Meili.Settings, as: :get!

  @spec update_settings(
          Client.t() | String.t(),
          String.t() | map() | Keyword.t() | nil,
          map() | Keyword.t() | nil
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate update_settings(client_or_uid, uid_or_settings \\ [], settings_or_nil \\ []),
    to: Meili.Settings,
    as: :update

  @spec update_settings!(
          Client.t() | String.t(),
          String.t() | map() | Keyword.t() | nil,
          map() | Keyword.t() | nil
        ) :: map() | no_return()
  defdelegate update_settings!(client_or_uid, uid_or_settings \\ [], settings_or_nil \\ []),
    to: Meili.Settings,
    as: :update!

  @spec reset_settings(Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate reset_settings(client_or_uid, uid_or_nil \\ nil), to: Meili.Settings, as: :reset

  @spec reset_settings!(Client.t() | String.t(), String.t() | nil) :: map() | no_return()
  defdelegate reset_settings!(client_or_uid, uid_or_nil \\ nil), to: Meili.Settings, as: :reset!

  # --- Experimental Features Delegates ---

  @spec get_experimental_features(Client.t() | nil) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate get_experimental_features(client \\ nil), to: Meili.Features, as: :get

  @spec get_experimental_features!(Client.t() | nil) :: map() | no_return()
  defdelegate get_experimental_features!(client \\ nil), to: Meili.Features, as: :get!

  @spec update_experimental_features(Client.t() | map() | Keyword.t(), map() | Keyword.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate update_experimental_features(client_or_features, features_or_nil \\ nil),
    to: Meili.Features,
    as: :update

  @spec update_experimental_features!(
          Client.t() | map() | Keyword.t(),
          map() | Keyword.t() | nil
        ) :: map() | no_return()
  defdelegate update_experimental_features!(client_or_features, features_or_nil \\ nil),
    to: Meili.Features,
    as: :update!

  # --- Network Delegates ---

  @spec get_network(Client.t() | nil) :: {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate get_network(client \\ nil), to: Meili.Network, as: :get

  @spec get_network!(Client.t() | nil) :: map() | no_return()
  defdelegate get_network!(client \\ nil), to: Meili.Network, as: :get!

  @spec update_network(Client.t() | map() | Keyword.t(), map() | Keyword.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  defdelegate update_network(client_or_params, params_or_nil \\ nil),
    to: Meili.Network,
    as: :update

  @spec update_network!(Client.t() | map() | Keyword.t(), map() | Keyword.t() | nil) ::
          map() | no_return()
  defdelegate update_network!(client_or_params, params_or_nil \\ nil),
    to: Meili.Network,
    as: :update!
end