cosmix-lib-client

cosmix-lib-client is the async Bus WebSocket client library for connecting Cosmix applications to a cosmix-noded broker. It occupies the bus end of the bus <- mix <- cos dependency chain: it depends on cosmix-lib-bus for wire messages and exposes broker-client primitives without depending on mix, cos, storage, or configuration loaders.

Synopsis

The Cargo package is cosmix-lib-client; Rust code imports cosmix_client.

The crate selects its NodedClient implementation by compilation target:

TargetClient model
Non-WASMNamed or anonymous Tokio client with incoming commands and replies
wasm32Anonymous, call-only browser client

Native builds also export the reconnecting SupervisedClient. The crate has no binary, command-line interface, or configuration file format.

Native client

NodedClient is the direct, one-connection client for non-WASM targets.

MethodPurpose
connectConnect with a caller-supplied URL and register a service name
connect_with_provenanceRegister a name and optional build provenance
connect_anonymousConnect without registering a service name
register_asRe-register the current connection under a new name

The caller supplies the broker URL. The native client does not read a config file and does not discover a broker URL.

Request methods:

MethodResult
callSends JSON arguments and returns a JSON value
call_typedSeparates transport failure from a Bus application error
call_with_headersSends caller headers and a verbatim body, then parses the reply
call_with_headers_rawReturns (rc, body, error_header) without collapsing application errors
sendSends JSON arguments without waiting for a reply
send_with_headersSends explicit Bus headers and a verbatim body without waiting
send_rawSends an already-built BusMessage

call treats a Bus return code of 10 or greater as an error. call_typed instead returns PortReply::AppError for that reply and reserves Err for transport failures. Successful typed replies retain the Bus return code, including warning codes.

The native request path correlates replies by message ID. A call waits for at most 60 seconds. Cancelling removes its pending entry. Unmatched replies are discarded.

call_with_headers protects the framing headers command, from, to, type, and id from caller overrides. call_with_headers_raw leaves the response body unparsed and preserves its optional error header.

Incoming commands and replies

incoming and incoming_async transfer ownership of the client's incoming command receiver. The receiver can be taken once.

Each IncomingCommand contains:

FieldMeaning
fromSending service
commandBus command name
idOptional request correlation ID
argsBody parsed as JSON, or null when absent or invalid
bodyOriginal body text
headersAll Bus headers

IncomingCommand::header reads any preserved header. target, parent, and source are shortcuts. is_ui_command tests for a ui. command prefix.

Use respond for an IncomingCommand; use respond_parts with its correlation parts.

Discovery and lifecycle

list_services returns registered service names. service_inventory returns cosmix_bus::ServiceInfo values with the broker's full service metadata.

is_connected reports the connection flag. deregister removes the registered name and clears the local name after success. close performs best-effort WebSocket shutdown, stops the reader task, marks the client disconnected, and releases pending callers.

Dropping a direct NodedClient does not by itself provide deterministic socket teardown. Call close when that guarantee is required.

Supervised client

SupervisedClient wraps a native NodedClient for resident services. Create it with connect_supervised or connect_supervised_with_provenance.

The initial connection has a bounded MAX_INITIAL_ATTEMPTS budget of five; exhaustion returns SupervisedError::InitialConnectFailed.

After an established connection is lost, the supervisor reconnects without an attempt limit. It uses full-jitter exponential backoff from 250 milliseconds to a 30-second ceiling. A successful reconnect re-registers the service, re-sends optional provenance, and replays recorded topic subscriptions in their original order.

The outward receiver returned by incoming survives transient reconnects. state returns ConnState, and connection_generation increments after each fully established connection. The states are Connecting, Connected, Disconnected, ShuttingDown, and Fatal.

That unbounded receiver remains the compatibility default. Opt in to a hard subscription memory bound with connect_options(...).bounded_incoming(capacity), then take incoming_bounded(). The socket reader and outward supervisor lane both use try_send; full lanes drop the new command and expose the loss as a cumulative overflow_count() plus a one-shot BoundedIncomingEvent::Overflow marker. Consumers can therefore reset derived state instead of silently continuing from an incomplete event stream.

Outbound supervised methods fail immediately unless the state is Connected. The client does not queue work while disconnected. SupervisedError::Disconnected and SupervisedError::ShuttingDown make those states distinguishable from SupervisedError::Transport.

The supervised client proxies the direct client's call, send, response, header, and service-list operations.

shutdown stops the supervisor without deregistering. deregister first stops the supervisor, then deregisters over the remaining live connection.

Topic subscriptions

subscribe_topic calls topic.subscribe and records the topic only after the broker accepts the subscription. unsubscribe_topic calls topic.unsubscribe and removes the topic only after broker acceptance.

SubscriptionRegistry is a cloneable, shared, ordered set. It provides record, remove, snapshot, len, and is_empty. Duplicate records are ignored. Removing and later recording a topic appends it at the end of replay order.

Browser client

On wasm32, NodedClient is an anonymous call-only client based on the browser WebSocket API. It does not register a service and does not receive incoming commands.

connect_anonymous accepts an explicit WebSocket URL. connect_anonymous_default derives /ws from the current page origin, using wss for HTTPS pages and ws otherwise. call sends a JSON request and waits for its correlated response. is_connected reports whether the reader loop remains live.

The browser client treats return codes of 10 or greater as errors and expects a non-empty successful response body to contain valid JSON.

Cargo features

FeatureDefaultEffect
nativeYesEnables Tokio, Tokio Tungstenite, native futures utilities, jitter generation, and cosmix-lib-bus native support

The browser backend is selected by the wasm32 target, not by a web Cargo feature. WASM dependencies include gloo-net, wasm-bindgen-futures, web-sys, futures-channel, and futures-util.

Example

use cosmix_client::{NodedClient, PortReply};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client =
    NodedClient::connect("alpha", "wss://broker.example.com/ws").await?;

match client
    .call_typed("beta", "status.get", serde_json::Value::Null)
    .await?
{
    PortReply::Ok { rc, value } => println!("rc={rc} value={value}"),
    PortReply::AppError { rc, message } => {
        eprintln!("application error rc={rc}: {message}");
    }
}

client.close().await;
Ok(())
}

PortReply is re-exported on non-WASM targets. The direct client returns anyhow::Result; the supervised client uses SupervisedError for lifecycle and transport failures.

Dependency boundary

The crate always depends on cosmix-lib-bus with that dependency's default features disabled. It also uses serde, serde_json, tracing, and anyhow.

Configuration loading, persistent state, broker deployment, and service implementations remain outside this crate. Native callers provide an explicit broker URL; browser callers may derive one only from the current page origin.