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//! # Platforms
9//!
10//! The Unix-socket transport is, unsurprisingly, Unix-only. **The TCP + mutual
11//! TLS transport is not** — it builds wherever rustls does, Windows included,
12//! as of 2.0.3. Before then it was gated on `unix` by accident: it lives in its
13//! own module but imported two types that happened to sit in the Unix one.
14//!
15//! # Throughput: one record per round-trip
16//!
17//! [`push`](WeirClient::push) is synchronous: it sends one frame and blocks for
18//! the ack before returning. So a **single** client's throughput is bounded by
19//! the round-trip time — roughly `1 / RTT` records/sec on one connection
20//! (e.g. a ~50 µs Unix-socket RTT caps a single client near ~20k rec/s; a
21//! `Durable`-tier push also waits on the daemon's fsync). To go faster, **fan out
22//! across connections**: create one `WeirClient` per producer thread (they're
23//! independent and the daemon handles many concurrently). Ordering is only
24//! guaranteed within a single connection's sequential pushes, not across
25//! connections. A built-in batched-push frame and a pooled client would need a
26//! `WIRE_VERSION` bump, so they remain a possible 3.0 addition; today, parallel
27//! connections are the way to scale a producer.
28//!
29//! # Example
30//!
31//! ```no_run
32//! # #[cfg(unix)] {
33//! use weir_client::{WeirClient, Durability}; // Durability is re-exported from weir-core
34//!
35//! let mut client = WeirClient::connect("/run/weir/weir.sock").unwrap();
36//! client.push(b"hello world", Durability::Durable).unwrap();
37//! # }
38//! ```
39//!
40//! # Ack vs. delivery
41//!
42//! A successful [`push`](WeirClient::push) means the record is **durably buffered
43//! at the requested [`Durability`] tier** — fsync'd to the write-ahead buffer for
44//! [`Durable`](Durability::Durable), in memory for
45//! [`Buffered`](Durability::Buffered). It does **not** mean the record has reached
46//! your downstream sink yet: the daemon drains buffered records to the sink in
47//! batches, only once a WAB segment seals (its size threshold, or daemon
48//! shutdown). For a small smoke test the sink may not be touched at all — watch
49//! `weir_records_ack_total` (acceptance), not the sink-commit metric, to confirm
50//! the daemon took your records.
51//!
52//! # Running the daemon
53//!
54//! This crate is the producer side; the daemon is the `weir-server` binary:
55//!
56//! ```text
57//! mkdir -p /run/weir/wab # the daemon does not create its directories
58//! weir-server --wab-dir /run/weir/wab --socket-path /run/weir/weir.sock
59//! ```
60//!
61//! `--wab-dir` must already exist. On macOS, do not place the socket directly in
62//! `/tmp` (it is a symlink the hardened bind rejects) — use a dedicated `0700`
63//! directory. Run `weir-server --help` for the full option list.
64//!
65//! # Observability
66//!
67//! The daemon serves Prometheus metrics at `127.0.0.1:9185/metrics` by default.
68//! The counters a producer cares about are labelled by tier/reason, e.g.
69//! `weir_records_accepted_total{tier="durable"}`,
70//! `weir_records_ack_total{tier="durable"}`, and
71//! `weir_records_nack_total{tier="durable",reason="empty_payload"}`.
72#![deny(missing_docs)]
73
74/// Transport-agnostic client core. Split out of `unix` so that `tls` — which
75/// needs a `TcpStream` and rustls, and nothing Unix-specific — is gated on the
76/// feature alone rather than on the platform.
77mod client;
78
79pub use client::{ClientError, DefaultTransport, WeirClient};
80
81#[cfg(unix)]
82mod unix;
83
84/// Re-export of [`weir_core::Durability`] so the common producer path needs a
85/// single crate import (`weir_client::Durability`).
86pub use weir_core::Durability;
87
88/// Re-export of [`weir_core::NackReason`] — the payload of [`ClientError::Nack`].
89/// Re-exported so consumers can match on the reason (e.g. to distinguish the
90/// connection-closing Nacks) without taking a direct dependency on `weir-core`.
91pub use weir_core::NackReason;
92
93#[cfg(feature = "tls")]
94mod tls;
95
96#[cfg(feature = "tls")]
97pub use tls::{ClientTlsConfig, TlsStream};