Packages

An Ash extension that brings full-text search to your resources via Meilisearch.

Current section

Files

Jump to
ash_meilisearch lib ash_meilisearch.ex
Raw

lib/ash_meilisearch.ex

defmodule AshMeilisearch do
@moduledoc """
An Ash extension that brings full-text search to your resources via Meilisearch.
Automatically configures Meilisearch indexes based on your resource configuration,
generates native `:search` read actions that work with normal Ash queries, and
keeps indexes in sync with your resources through CRUD hooks.
## Usage
defmodule MyApp.Blog.Post do
use Ash.Resource,
domain: MyApp.Blog,
extensions: [AshMeilisearch]
meilisearch do
index "posts"
searchable_attributes [:title, :content, author: [:name]]
filterable_attributes [:status, :published_at]
sortable_attributes [:title, :published_at]
end
changes do
change AshMeilisearch.Changes.UpsertSearchDocument, on: [:create, :update]
change AshMeilisearch.Changes.DeleteSearchDocument, on: [:destroy]
end
end
## Generated Features
- `search_document` calculation for Meilisearch indexing
- `:search` read action for fast search/filter/sort operations
- `:reindex` and `:configure_index` update actions for index management
- Automatic conversion of Ash.Filter and Ash.Sort to Meilisearch expressions
"""
use Spark.Dsl.Extension,
sections: [AshMeilisearch.Dsl.meilisearch_section()],
transformers: [
AshMeilisearch.Transformers.AddSearchDocument,
AshMeilisearch.Transformers.AddSearchAction,
AshMeilisearch.Transformers.AddIndexActions
],
verifiers: [
AshMeilisearch.Verifiers.ValidateSearchConfig
]
# Re-export for convenience - using the actual generated function names
def index_name(resource) do
case AshMeilisearch.Info.meilisearch_index(resource) do
{:ok, value} -> AshMeilisearch.Config.index_name(value)
value -> AshMeilisearch.Config.index_name(value)
end
end
def action_name(resource) do
case AshMeilisearch.Info.meilisearch_action_name(resource) do
{:ok, value} -> value
value -> value
end
end
defdelegate primary_key(resource), to: AshMeilisearch.Info, as: :meilisearch_primary_key
defdelegate searchable_attributes(resource), to: AshMeilisearch.Info
defdelegate filterable_attributes(resource), to: AshMeilisearch.Info
defdelegate sortable_attributes(resource), to: AshMeilisearch.Info
@doc """
Build Meilisearch filter from an Ash.Query.
## Examples
iex> query = Ash.Query.filter(MyApp.Post, status: :published)
iex> AshMeilisearch.build_filter(query)
"status = published"
"""
def build_filter(%Ash.Query{} = query),
do: AshMeilisearch.FilterBuilder.build_filter(query.filter)
@doc """
Build Meilisearch sort array from an Ash.Query.
## Examples
iex> query = Ash.Query.sort(MyApp.Post, [inserted_at: :desc, title: :asc])
iex> AshMeilisearch.build_sort(query)
["inserted_at:desc", "title:asc"]
"""
def build_sort(%Ash.Query{} = query),
do: AshMeilisearch.SortBuilder.build_sort(query.sort, query.resource)
## Client convenience functions
@doc """
Performs a search on the resource's Meilisearch index.
Automatically resolves the index name from the resource configuration.
## Examples
AshMeilisearch.search(MyApp.Post, "elixir")
AshMeilisearch.search(MyApp.Post, "phoenix", limit: 10, filter: "status = published")
"""
def search(resource, query, opts \\ []) do
index_name = index_name(resource)
AshMeilisearch.Client.search(index_name, query, opts)
end
@doc """
Performs a multisearch across multiple queries on the resource's index.
Automatically resolves the index name from the resource configuration.
## Examples
queries = [
%{q: "elixir", limit: 5},
%{q: "phoenix", limit: 5}
]
AshMeilisearch.multisearch(MyApp.Post, queries, federation: %{limit: 10})
"""
def multisearch(resource, queries, opts \\ []) when is_list(queries) do
index_name = index_name(resource)
# Add index name to each query if not already present
queries_with_index =
Enum.map(queries, fn query ->
if Map.has_key?(query, :indexUid) do
query
else
Map.put(query, :indexUid, index_name)
end
end)
AshMeilisearch.Client.multisearch(queries_with_index, opts)
end
@doc """
Adds documents to the resource's Meilisearch index.
Automatically resolves the index name from the resource configuration.
## Examples
documents = [%{id: 1, title: "Hello"}, %{id: 2, title: "World"}]
AshMeilisearch.add_documents(MyApp.Post, documents)
"""
def add_documents(resource, documents) do
index_name = index_name(resource)
AshMeilisearch.Client.add_documents(index_name, documents)
end
@doc """
Deletes a document from the resource's Meilisearch index.
Automatically resolves the index name from the resource configuration.
## Examples
AshMeilisearch.delete_document(MyApp.Post, "doc-id-123")
"""
def delete_document(resource, document_id) do
index_name = index_name(resource)
AshMeilisearch.Client.delete_document(index_name, document_id)
end
end