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.1.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.