lib/typesense/documents.ex

defmodule Typesense.Documents do
  @moduledoc """
  Methods for interaction with Typesense Documents.
  """

  @doc """
  Index a document.

  ## Examples

  ```elixir
  iex> document = %{
    company_name: "Stark Industries",
    num_employees: 5215,
    country: "USA"
  }
  iex> Typesense.Documents.create(collection, document)
  {:ok, document}
  ```
  """
  @spec create(binary(), binary() | map()) :: {:ok, map()} | {:error, any()}
  def create(collection, document) do
    Typesense.post("/collections/#{collection}/documents", document)
  end

  @doc """
  Retrieve a document.

  ## Examples

  ```elixir
  iex> Typesense.Documents.retrieve(collection, id)
  {:ok, document}
  ```
  """
  def retrieve(collection, id) do
    Typesense.get("/collections/#{collection}/documents/#{id}")
  end

  @doc """
  Search for documents.

  ## Examples

  ```elixir
  iex> search_params = %{
    q: "stark",
    query_by: "company_name",
    filter_by: "num_employees:>100",
    sort_by: "num_employees:desc"
  }
  iex> Typesense.Documents.search(collection, search_params)
  iex> {:ok, documents}
  ```
  """
  def search(collection, query) do
    Typesense.get("/collections/#{collection}/documents/search", [], params: query)
  end

  @doc """
  Update a document.

  ## Examples

  ```elixir
  iex> Typesense.Documents.update(collection, id, document)
  {:ok, document}
  ```
  """
  # NOTE: Need to update all the others that take a document to specify binary or map?
  @spec update(binary(), binary(), binary() | map()) :: {:ok, map()} | {:error, any()}
  def update(collection, id, document) do
    Typesense.patch("/collections/#{collection}/documents/#{id}", document)
  end

  @doc """
  Delete a document by id, or several documents through a query.

  ## Examples

  ```elixir
  iex> Typesense.Documents.delete(collection, id)
  {:ok, _document}
  ```
  """
  @spec delete(binary(), binary() | integer() | map()) :: {:ok, map()} | {:error, any()}
  def delete(collection, id) when is_integer(id) or is_binary(id) do
    Typesense.delete("/collections/#{collection}/documents/#{id}")
  end

  def delete(collection, query) when is_map(query) do
    Typesense.delete("/collections/#{collection}/documents", [], params: query)
  end

  @doc """
  Export documents from a collection.

  ## Examples

  ```elixir
  iex> Typesense.Documents.export(collection)
  [%{}, ...]
  """
  def export(collection) do
    Typesense.get("/collections/#{collection}/documents/export", [], parse: :jsonl)
  end

  @doc """
  Import documents into a collection.

  ## Examples

  ```elixir
  iex> documents = [%{
    id: "124",
    company_name: "Stark Industries",
    num_employees: 5215,
    country: "USA"
  }]
  iex> Typesense.Documents.import(collection, documents, :create)
  {:ok, documents}
  """
  def import(collection, documents, action \\ :create) do
    Typesense.post(
      "/collections/#{collection}/documents/import?action=#{action}",
      prepare_jsonl(documents),
      [],
      parse: :jsonl
    )
  end

  defp prepare_jsonl(documents) when is_list(documents) do
    Enum.map_join(documents, "\n", &Jason.encode!/1)
  end

  defp prepare_jsonl(documents) when is_binary(documents) do
    documents
  end
end