weir_client/lib.rs
1//! Synchronous blocking client for the weir daemon.
2//!
3//! Connects over a Unix domain socket (or TCP + mutual TLS behind the `tls`
4//! feature) and exchanges frames using the weir wire protocol v1. Each method
5//! issues one request and reads one response — no pipelining. For concurrent
6//! producers, create one [`WeirClient`] per thread.
7//!
8//! # Throughput: one record per round-trip
9//!
10//! [`push`](WeirClient::push) is synchronous: it sends one frame and blocks for
11//! the ack before returning. So a **single** client's throughput is bounded by
12//! the round-trip time — roughly `1 / RTT` records/sec on one connection
13//! (e.g. a ~50 µs Unix-socket RTT caps a single client near ~20k rec/s; a
14//! `Durable`-tier push also waits on the daemon's fsync). To go faster, **fan out
15//! across connections**: create one `WeirClient` per producer thread (they're
16//! independent and the daemon handles many concurrently). Ordering is only
17//! guaranteed within a single connection's sequential pushes, not across
18//! connections. A built-in batched-push frame and a pooled client are a possible
19//! future (2.0) wire-protocol addition; today, parallel connections are the way
20//! to scale a producer.
21//!
22//! # Example
23//!
24//! ```no_run
25//! # #[cfg(unix)] {
26//! use weir_client::{WeirClient, Durability}; // Durability is re-exported from weir-core
27//!
28//! let mut client = WeirClient::connect("/run/weir/weir.sock").unwrap();
29//! client.push(b"hello world", Durability::Durable).unwrap();
30//! # }
31//! ```
32//!
33//! # Ack vs. delivery
34//!
35//! A successful [`push`](WeirClient::push) means the record is **durably buffered
36//! at the requested [`Durability`] tier** — fsync'd to the write-ahead buffer for
37//! [`Durable`](Durability::Durable), in memory for
38//! [`Buffered`](Durability::Buffered). It does **not** mean the record has reached
39//! your downstream sink yet: the daemon drains buffered records to the sink in
40//! batches, only once a WAB segment seals (its size threshold, or daemon
41//! shutdown). For a small smoke test the sink may not be touched at all — watch
42//! `weir_records_ack_total` (acceptance), not the sink-commit metric, to confirm
43//! the daemon took your records.
44//!
45//! # Running the daemon
46//!
47//! This crate is the producer side; the daemon is the `weir-server` binary:
48//!
49//! ```text
50//! mkdir -p /run/weir/wab # the daemon does not create its directories
51//! weir-server --wab-dir /run/weir/wab --socket-path /run/weir/weir.sock
52//! ```
53//!
54//! `--wab-dir` must already exist. On macOS, do not place the socket directly in
55//! `/tmp` (it is a symlink the hardened bind rejects) — use a dedicated `0700`
56//! directory. Run `weir-server --help` for the full option list.
57//!
58//! # Observability
59//!
60//! The daemon serves Prometheus metrics at `127.0.0.1:9185/metrics` by default.
61//! The counters a producer cares about are labelled by tier/reason, e.g.
62//! `weir_records_accepted_total{tier="durable"}`,
63//! `weir_records_ack_total{tier="durable"}`, and
64//! `weir_records_nack_total{tier="durable",reason="empty_payload"}`.
65#![deny(missing_docs)]
66
67#[cfg(unix)]
68mod unix;
69
70#[cfg(unix)]
71pub use unix::{ClientError, WeirClient};
72
73/// Re-export of [`weir_core::Durability`] so the common producer path needs a
74/// single crate import (`weir_client::Durability`).
75pub use weir_core::Durability;
76
77/// Re-export of [`weir_core::NackReason`] — the payload of [`ClientError::Nack`].
78/// Re-exported so consumers can match on the reason (e.g. to distinguish the
79/// connection-closing Nacks) without taking a direct dependency on `weir-core`.
80pub use weir_core::NackReason;
81
82#[cfg(all(unix, feature = "tls"))]
83mod tls;
84
85#[cfg(all(unix, feature = "tls"))]
86pub use tls::{ClientTlsConfig, TlsStream};