Dowser.OpenSearch
An Elixir client for the OpenSearch API, built on top
of Dowser.Client.
The library follows the OpenSearch OpenAPI specification: each of its tags maps to one module, and each endpoint to one function named after the operation.
Installation
Add dowser_opensearch to your dependencies in mix.exs:
def deps do
[
{:dowser_opensearch, "~> 0.2.0"}
]
end
Point it at a cluster through a Dowser.Client context:
config :dowser_client,
contexts: [
default: [endpoint: "http://localhost:9200"]
]
Usage
Every API function is reached through its own module — there are no shortcuts on the top-level Dowser.OpenSearch namespace:
{:ok, info} = Dowser.OpenSearch.Info.info()
info["version"]["distribution"]
#=> "opensearch"
Each function comes in two variants: the plain one returns {:ok, body} or
{:error, exception}, and the bang one returns the body directly or raises:
Dowser.OpenSearch.Search.search!(%{query: %{match_all: %{}}}, index: "posts")
Required OpenSearch attributes are positional arguments; everything optional —
including the transport options forwarded to Dowser.Client — lives in the
trailing keyword list. A request body is always the first argument, so calls
pipe:
%{query: %{term: %{"status" => "published"}}}
|> Dowser.OpenSearch.Search.search(index: "posts", params: [size: 50])
HEAD checks come as a pair where the ? variant plays the bang role:
Dowser.OpenSearch.Index.index_exists("posts") #=> {:ok, true}
Dowser.OpenSearch.Index.index_exists?("posts") #=> true
Modules
| Module | Tag |
|---|---|
Dowser.OpenSearch.Alias |
Aliases |
Dowser.OpenSearch.Cat |
CAT |
Dowser.OpenSearch.Cluster |
Cluster |
Dowser.OpenSearch.DanglingIndices |
Dangling Indices |
Dowser.OpenSearch.DataStream |
Data Streams |
Dowser.OpenSearch.Document |
Document |
Dowser.OpenSearch.Index |
Index |
Dowser.OpenSearch.IndexSettings |
Index Settings |
Dowser.OpenSearch.IndexStateManagement |
Index State Management |
Dowser.OpenSearch.IndexTemplate |
Index Templates |
Dowser.OpenSearch.Info |
Info |
Dowser.OpenSearch.List |
List |
Dowser.OpenSearch.Mappings |
Mappings |
Dowser.OpenSearch.Reindex |
Reindex |
Dowser.OpenSearch.Search |
Search |
Type casting
Wire Dowser.OpenSearch.Codec in as the decoder and encoder, and documents are
cast against their own index mapping — dates become DateTime, IPs become
:inet tuples, and so on:
config :dowser_client,
contexts: [
default: [
endpoint: "http://localhost:9200",
decoder: Dowser.OpenSearch.Codec,
encoder: Dowser.OpenSearch.Codec
]
]
Dowser.OpenSearch.Document.get!("posts", "1")["_source"]["published_at"]
#=> ~U[2026-08-11 00:00:00.000Z]
Mappings are fetched and cached by Dowser.OpenSearch.MappingCacher.
Streaming
Dowser.OpenSearch.Streamer walks more documents than fit in one response,
over a point in time:
%{query: %{match_all: %{}}, sort: [%{"_id" => "asc"}]}
|> Dowser.OpenSearch.Streamer.stream(index: "posts")
|> Stream.map(& &1["_source"])
|> Enum.each(&process/1)
The sort is required and its last key must be unique per document —
OpenSearch has no _shard_doc to supply a tiebreaker, and search_after on a
non-unique key silently skips rows. See the module documentation.
Repositories
Dowser.OpenSearch.Repository binds the index-related functions to a fixed or
computed index:
defmodule Posts do
use Dowser.OpenSearch.Repository, index: "posts", only: [search: [:search], document: [:get]]
end
Posts.search!(%{query: %{match_all: %{}}}) # searches /posts/_search
Posts.get_doc!("1") # GETs /posts/_doc/1
Documentation
The full API reference is on HexDocs.
License
MIT — see LICENSE.txt.