What it is
stryke-search is an Elasticsearch / OpenSearch client for
stryke.
It covers index administration, document CRUD, bulk indexing, the full
query DSL, aggregations, scroll and point-in-time pagination, aliases,
templates, ingest pipelines, snapshots, stored scripts, tasks, and
cluster / node health against any Elasticsearch 7+/8+ or OpenSearch
1+/2+ cluster. Both engines speak the same REST API, so one client
covers both without version-locked SDK types.
It ships as a Rust cdylib that stryke dlopens in-process
on the first use Search and registers every
search__* export. A ureq HTTP keep-alive
connection pool (a ureq::Agent) is cached per
(base_url, auth) tuple for the life of the process, so
repeated calls reuse pooled sockets instead of reconnecting. There is
no tokio and no OpenSSL — the transport is pure-Rust over rustls,
which keeps the dependency tree vendorable and the build reproducible.
The public surface is 135 functions in the
Search namespace.
Install
s add github.com/MenkeTechnologies/stryke-search
This records the dependency in your project's stryke.toml
and downloads the prebuilt cdylib for your host triple from the GitHub
release (SHA-256 verified). On first use Search stryke
dlopens the cdylib in-process. Prebuilt targets are
x86_64-unknown-linux-gnu,
aarch64-unknown-linux-gnu,
x86_64-apple-darwin, and
aarch64-apple-darwin. Then use Search in any
stryke script.
Quick start
use Search
var %conn
$conn{url} = "http://127.0.0.1:9200"
# create an index with a mapping
Search::index_create(
"books",
body => { mappings => { properties => { title => { type => "text" }, year => { type => "integer" } } } },
%conn,
)
# index a document and make it searchable
Search::doc_index("books", { title => "rust in action", year => 2019 }, id => "1", %conn)
Search::index_refresh(index => "books", %conn)
# search it
val $res = Search::search("books", Search::match("title", "rust"), %conn)
p $res->{hits}{total}{value} # 1
# count with a range query
val $cnt = Search::count("books", Search::range("year", gte => 2019), %conn)
p $cnt->{count} # 1
Connecting
Connection params come from the %conn opts hash on every
network call. When neither url nor host is
given, the client falls back to the $SEARCH_URL
environment variable. The pure query-DSL builders take no connection.
| url | Full base URL, e.g. https://es.example.com:9243. No default. |
| host | Used with port / tls when url is absent. Default 127.0.0.1. |
| port | Default 9200. |
| tls | true selects the https scheme. Default false. |
| username / password | HTTP Basic auth. |
| api_key | Sent as Authorization: ApiKey <key>; wins over Basic when both are set. |
| params | Hash of extra URL query params merged into the request. |
# explicit host/port/tls with basic auth
var %conn
$conn{host} = "es.example.com"
$conn{port} = 9243
$conn{tls} = true
$conn{username} = "elastic"
$conn{password} = "changeme"
# or one URL, or an API key
$conn{url} = "https://es.example.com:9243"
$conn{api_key} = "BASE64_ENCODED_KEY"
# or rely on $SEARCH_URL: SEARCH_URL=http://127.0.0.1:9200 s run app.stk
API surface
The 135-function FFI surface, grouped. Network ops take a trailing
%conn opts hash; the query-DSL, aggregation, body, and URL
builders are pure and take no connection.
| Version + liveness | version, ping, info, health |
| Cluster + nodes | cluster_stats, cluster_state, cluster_settings_get, cluster_settings_put, nodes_info, nodes_stats, pending_tasks, allocation_explain, cat, raw |
| Index admin | index_create, index_delete, index_exists, index_list, index_refresh, index_open, index_close, index_stats, forcemerge, flush, clear_cache, settings_get, settings_update, mapping_get, mapping_put, field_caps |
| Aliases | alias_add, alias_remove, alias_get |
| Templates | index_template_put, index_template_get, index_template_delete, index_template_exists, component_template_put, component_template_get, component_template_delete |
| Documents | doc_index, doc_get, doc_exists, doc_update, doc_delete, mget, bulk, termvectors, mtermvectors |
| Search | search, count, msearch, search_aggs, search_shards, scroll_start, scroll_next, scroll_clear, delete_by_query, update_by_query, reindex, analyze, explain, pit_open, pit_close |
| Query DSL builders | match_all, match, match_phrase, match_phrase_prefix, term, terms, range, prefix, wildcard, regexp, fuzzy, exists, ids, query_string, simple_query_string, multi_match, geo_distance, geo_bounding_box, nested, constant_score, dis_max, function_score, more_like_this, bool |
| Aggregations | agg, agg_terms, agg_avg, agg_sum, agg_min, agg_max, agg_stats, agg_extended_stats, agg_cardinality, agg_value_count, agg_percentiles, agg_histogram, agg_date_histogram, agg_range, agg_filter, agg_missing, agg_nested, agg_top_hits, agg_global |
| Body + fields | query_body, sort, highlight, bulk_ndjson, escape |
| Ingest pipelines | ingest_put, ingest_get, ingest_delete, ingest_simulate |
| Snapshot + repo | repo_create, repo_get, repo_delete, snapshot_create, snapshot_get, snapshot_delete, snapshot_restore |
| Tasks | tasks_list, tasks_get, tasks_cancel |
| Stored scripts + templates | script_put, script_get, script_delete, search_template, render_template |
| URL helpers | build_url, parse_url, redact_url |
| Validation | valid_index_name |
The full reference lives in the README; the architecture is in the engineering report.
Queries + aggregations
Query builders return bare clauses (e.g.
{"match":{…}}), so they nest directly inside
bool / nested / constant_score.
Search::search and Search::count auto-wrap a
top-level clause as {"query": clause}. Pass a full body
built with Search::query_body when you need
aggs / sort / size.
# bool query: must match title, filter by year range
val $q = Search::bool(
must => [ Search::match("title", "rust") ],
filter => [ Search::range("year", gte => 2018) ],
)
val $hits = Search::search("books", $q, %conn)
# aggregations: average price bucketed by category
val $res = Search::search_aggs(
"products",
{ by_cat => Search::agg_terms("category", size => 10) },
%conn,
)
p $res->{aggregations}{by_cat}{buckets}
# full body with query + sort + size
val $body = Search::query_body(
query => Search::match("title", "rust"),
sort => Search::sort("year", order => "desc"),
size => 20,
)
val $page = Search::search("books", $body, %conn)
The builders are pure — they construct clauses with no cluster present:
# geo bounding box: docs whose `loc` is inside the box
Search::geo_bounding_box("loc", { lat => 40.73, lon => -74.1 }, { lat => 40.01, lon => -71.12 })
# function_score: re-score with a random function
Search::function_score(Search::match_all(), [{ random_score => {} }], boost_mode => "multiply")
# more_like_this across two fields
Search::more_like_this(["title", "body"], "quick brown fox", min_term_freq => 1)
# top_hits aggregation
Search::agg_top_hits(size => 3)
Bulk + pagination
Bulk ops are an array of op hashes; the cdylib serializes the NDJSON
body. Search::bulk_ndjson builds the same body without
sending it, for inspection or batching.
Search::bulk(
[
{ action => "index", id => "2", document => { title => "the rust book" } },
{ action => "update", id => "1", doc => { year => 2020 } },
{ action => "delete", id => "3" },
],
index => "books",
%conn,
)
# scroll through a large result set
val $first = Search::scroll_start("books", Search::match_all(), scroll => "1m", %conn)
val $more = Search::scroll_next($first->{_scroll_id}, %conn)
Search::scroll_clear($first->{_scroll_id}, %conn)
URL helpers + validation
The URL helpers and valid_index_name are pure and need no
cluster. redact_url strips credentials for safe logging.
Search::build_url(host => "h", port => 9200) # "http://h:9200"
Search::redact_url("https://u:p\@h:9200") # "https://***\@h:9200"
Search::escape("a+b") # "a\\+b" (Lucene-escaped)
val $pu = Search::parse_url("https://es.example.com:9243/prefix")
p $pu->{host} # "es.example.com"
p $pu->{port} # 9243
Search::valid_index_name("Books") # { name, valid, reason }
Errors
When the cdylib reports an error, the wrapper dies with a
message of the form Search::<fn>: <reason> —
e.g. a missing index or a malformed query body surfaces as a raised
stryke exception rather than a silent { error: … } hash.
FFI handlers run inside catch_unwind, so a panic becomes
an error result instead of unwinding across the FFI boundary. Wrap
calls in try / catch to handle failures.
Build + test
make debug # cargo build
make test # cargo test, then `s test t/` (needs $SEARCH_URL or 127.0.0.1:9200)
make install # s pkg install -g . (cdylib lands in ~/.stryke/store/search@<ver>/)
cargo test runs the in-crate unit tests (NDJSON build,
Lucene escaping, URL parse / redact, basic-auth header,
percent-encoding) with no cluster required.
t/test_stryke_search_surface.stk pins the wrapper surface
and the query-DSL builders;
t/test_search.stk runs end-to-end CRUD + search against a
live cluster and short-circuits when none answers.
Package family
Part of the stryke connector family under the MenkeTechnologiesMeta umbrella:
- stryke-mongo — MongoDB
- stryke-postgres — PostgreSQL
- stryke-redis — Redis / Valkey
- stryke-duckdb — embedded DuckDB
- stryke-kafka — Apache Kafka
Sibling packages
Part of the stryke package family. Browse the others via the MenkeTechnologiesMeta umbrella repo:
- stryke-arrow — Apache Arrow / Parquet / Feather
- stryke-aws — S3, DynamoDB, SQS, Lambda, STS
- stryke-azure — Blob, Queues, Cosmos DB, Key Vault, Entra
- stryke-clickhouse — ClickHouse
- stryke-demo — live demos for every connector
- stryke-docker — Docker daemon API
- stryke-duckdb — embedded DuckDB SQL engine
- stryke-email — email + campaign client
- stryke-fleet — parallel expect/PTY automation
- stryke-gcp — Cloud Storage, Pub/Sub, Secret Manager, BigQuery, Firestore
- stryke-grpc — reflection-based gRPC client
- stryke-gui — mouse / keyboard / screen / clipboard automation
- stryke-k8s — Kubernetes
- stryke-kafka — Apache Kafka
- stryke-mcpd — MCP servers without a runtime
- stryke-mongo — MongoDB
- stryke-mssql — Microsoft SQL Server
- stryke-mysql — MySQL / MariaDB
- stryke-neo4j — Neo4j graph (Bolt)
- stryke-office — Office docs / PDF / images
- stryke-parquet — Parquet file inspector
- stryke-polars — Polars + ndarray + linalg + FFT
- stryke-postgres — PostgreSQL
- stryke-redis — Redis / Valkey
- stryke-scrape — web scraping / crawling
- stryke-scylla — ScyllaDB / Cassandra
- stryke-selenium — browser automation (WebDriver)
- stryke-spark — Spark Connect (no JVM)
- stryke-utils — pure-stryke utility belt
- stryke-zmq — ZeroMQ messaging