Generates a CRUD/read repository for an Orkestra.ES.Schema.
A repository binds a schema to a Snap.Cluster and exposes an idiomatic,
tuple-returning API over Elasticsearch/OpenSearch: single-document
get/2, save/2, delete/2, bulk save_all/2, count/2, a lazy
stream/1, refresh/1, and a raw search/2 escape hatch. Every function
accepts a trailing opts :: keyword() with an optional :culture for
multi-culture schemas.
Defining a repository
defmodule MyApp.Search.Products do
use Orkestra.ES.Repository,
schema: MyApp.Search.Product,
cluster: MyApp.ESCluster
endBoth :schema and :cluster are required; omitting either raises an
ArgumentError at compile time.
Generated API
get(id, opts \\ [])—{:ok, struct}|{:error, :not_found}|{:error, term}.save(struct, opts \\ [])— upsert;{:ok, struct}|{:error, term}.save_all(structs, opts \\ [])— bulk upsert;:ok|{:error, %Snap.BulkError{}}|{:error, term}.delete(id, opts \\ [])—:ok|{:error, :not_found}|{:error, term}.count(opts \\ [])—{:ok, non_neg_integer}|{:error, term}.stream(opts \\ [])— a lazyEnumerableof schema structs.refresh(opts \\ [])—:ok|{:error, term}.search(query, opts \\ [])—{:ok, %Snap.SearchResponse{}}|{:error, term}.get_paged(opts \\ [])— paginated/faceted query;{:ok, %Orkestra.ES.Page{}}|{:error, term}.__es_repository__(:schema | :cluster)— introspection.
All generated functions are defoverridable, so a repository may redefine
any of them (e.g. to add caching) and delegate with super/….
Culture resolution
The document _id comes from the schema's primary_key field. The target
index alias is resolved from the schema and the optional :culture option:
- no
:culture— the schema default alias (multi-culture) or the single unsuffixed alias (mono-culture). :cultureon a multi-culture schema — the per-culture alias, or{:error, {:unknown_culture, culture, valid_cultures}}when the culture is not declared.:cultureon a mono-culture schema — always{:error, {:unknown_culture, culture, []}}, since mono-culture schemas accept no culture argument.
Culture is validated before the schema's alias_for/1 is called (that
function raises), so the tuple-returning functions never raise on an unknown
culture. stream/1 is the sole exception: because it must return an
Enumerable rather than a tuple, it raises ArgumentError on an unknown
culture.
count/2, stream/1 and search/2 queries
count/2 and stream/1 accept an optional :query in opts:
- omitted — matches all documents.
- an
%Orkestra.ES.Query{}— built viaOrkestra.ES.Query.build/1; only its"query"clause is used. - a raw map — used as-is when it already carries a
"query"key, otherwise wrapped as%{"query" => map}.
search/2 takes the query as its first argument: either an
%Orkestra.ES.Query{} (built into a full request body) or a raw request
map. Hits are not decoded — the caller inspects the
%Snap.SearchResponse{} and may rebuild structs with the schema's
from_hit/1 on each hit.source.
Observability
Every public function opens an OpenTelemetry span
(orkestra.es.get, orkestra.es.save, …) with "es.index", "es.culture"
and "orkestra.es.schema" attributes (plus "es.doc_count" for
save_all/2) and emits a [:orkestra, :es, :request] :telemetry event
with %{duration_ms: …} measurements and %{op:, index:, culture:, schema:, result:} metadata. stream/1 spans only the opening of the stream — the
actual scroll requests happen lazily as the consumer pulls elements. Cluster
credentials and adapter options are never logged (convention T-08-02).