# aion-client
Rust caller SDK for connecting to an Aion server deployment and operating Aion workflows. The crate exposes connect plus the seven workflow operations: `start`, `signal`, `query`, `cancel`, `list`, `describe`, and `subscribe`.
## Key public types
- `Client`, `ClientBuilder`, `ClientAuth`, and `TlsOptions` configure gRPC connections.
- `WorkflowHandle` scopes operations to a started workflow run.
- `StartOptions`, `WorkflowDescription`, and `ListPage` model workflow operations.
- `EventStream`, `ResumingEventStream`, and `SubscribeTarget` stream events.
- `to_payload` and `from_payload` bridge typed values with `aion-core` payloads.
## Install
```toml
[dependencies]
aion-client = "0.4.0"
```
## Server prerequisite
Run an Aion server (`aion server --config <file>`) that implements the AW workflow API. The runnable example uses the AL-007 server fixture defaults:
```sh
export AION_SERVER_URL=http://127.0.0.1:50051
export AION_AUTH_TOKEN=dev-token # optional
cargo run -p aion-client --example seven_operations
```
See [`examples/seven_operations.rs`](examples/seven_operations.rs) for a complete program covering all seven operations.
## Connect
```rust
use aion_client::{ClientAuth, ClientBuilder};
let mut builder = ClientBuilder::new(std::env::var("AION_SERVER_URL")?)
.with_namespace("conformance");
if let Ok(token) = std::env::var("AION_AUTH_TOKEN") {
builder = builder.with_auth(ClientAuth::bearer(token));
}
let client = builder.build().await?;
```
## start
`start_typed` serializes a typed value to JSON and returns a `WorkflowHandle` that carries the workflow and run IDs. `StartOptions::idempotency_key` makes caller retries safe: the same request returns the original handle and conflicting reuse returns `ClientError::AlreadyExists`.
```rust
use aion_client::StartOptions;
use serde::Serialize;
#[derive(Serialize)]
struct StartInput {
message: &'static str,
counter: u32,
}
let handle = client
.start_typed(
"conformance.echo",
&StartInput { message: "hello", counter: 1 },
StartOptions {
idempotency_key: Some("readme-seven-operations".to_owned()),
..StartOptions::default()
},
)
.await?;
```
## signal
```rust
#[derive(Serialize)]
struct SignalInput {
value: &'static str,
}
handle
.signal_typed("record", &SignalInput { value: "signal-observed" })
.await?;
```
## query
Query results can be decoded into any `serde::Deserialize` type. The current AW query request has no argument payload field, so pass `&()` for typed query arguments.
```rust
use serde::Deserialize;
use std::time::Duration;
#[derive(Deserialize)]
struct EchoState {
last_signal: Option<String>,
}
let state: EchoState = handle.query_typed("state", &(), Duration::from_secs(5)).await?;
```
## list
```rust
use aion_client::ListPage;
use aion_core::WorkflowFilter;
let summaries = client
.list(
&WorkflowFilter {
workflow_type: Some("conformance.echo".to_owned()),
..WorkflowFilter::default()
},
ListPage::default(),
)
.await?;
```
## describe
```rust
let description = handle.describe().await?;
println!("history events: {}", description.history.len());
```
## cancel
Cancellation is a cooperative request: success means the server accepted it, not that the workflow has already reached a terminal state.
```rust
handle.cancel("caller requested cancellation").await?;
```
## subscribe
`subscribe` returns a `Stream<Item = Result<Event, ClientError>>`. Transient disconnects are retried with the next per-workflow sequence number so delivered events are gap-free and duplicate-free; terminal failures are yielded as stream errors.
```rust
use futures::StreamExt;
let mut events = handle.subscribe();
while let Some(event) = events.next().await {
let event = event?;
println!("event seq={}", event.seq());
break;
}
```
## Typed and raw payloads
Typed helpers (`start_typed`, `signal_typed`, `query_typed`, `to_payload`, `from_payload`) use JSON by default. For pre-serialized or non-JSON data, use the raw `aion_core::Payload` escape hatch with the raw operation variants:
```rust
use aion_core::{ContentType, Payload};
let raw = Payload::new(ContentType::Json, br#"{"value":"raw"}"#.to_vec());
handle.signal("record", raw).await?;
```
## Branching on errors
Every operation returns the shared branchable taxonomy via `ClientError`.
Each variant carries an `ErrorDetail` with the server's human detail message
and, when the wire supplied one, the structured `error_type` discriminator.
```rust
use aion_client::ClientError;
match handle.query_typed::<_, serde_json::Value>("state", &(), Duration::from_millis(10)).await {
Ok(value) => println!("state: {value}"),
Err(ClientError::QueryTimeout { detail }) => eprintln!("query timed out: {detail}"),
Err(ClientError::UnknownQuery { detail }) => eprintln!("unknown query: {detail}"),
Err(ClientError::Unavailable { detail }) => eprintln!("server is unavailable: {detail}"),
Err(error) => return Err(error),
}
```