wave-api 0.1.0

Typed Rust client for the Wave Accounting GraphQL API
Documentation
# wave-api

Typed Rust client for the [Wave Accounting](https://www.waveapps.com/) GraphQL API.

No code generation, no raw GraphQL strings in user code. All connection types are flattened into `Page<T>` results, monetary values use `rust_decimal::Decimal`, and mutations use a builder pattern for inputs.

## Features

- **OAuth2 authentication** with automatic token refresh on `UNAUTHENTICATED` errors
- **Strongly typed** queries, mutations, inputs, and enums
- **Pagination** — GraphQL connections are flattened into simple `Page<T>` results
- **Decimal precision** — all monetary values use `rust_decimal::Decimal`
- **Builder pattern** for mutation inputs with optional field skipping

## Quick Start

Add to your `Cargo.toml`:

```toml
[dependencies]
wave-api = "0.1"
```

```rust
use wave_api::{WaveClient, OAuthConfig};

let client = WaveClient::with_oauth(OAuthConfig {
    client_id: "...".into(),
    client_secret: "...".into(),
    access_token: "...".into(),
    refresh_token: "...".into(),
    redirect_uri: "http://localhost:3099/callback".into(),
    on_token_refresh: None,
});

let businesses = client.list_businesses(Default::default()).await?;
for biz in &businesses.items {
    println!("{}: {}", biz.id, biz.name);
}
```

## API Coverage

### Queries

| Resource   | Methods                             |
|------------|-------------------------------------|
| User       | `get_user`                          |
| Business   | `list_businesses`, `get_business`   |
| Customer   | `list_customers`, `get_customer`    |
| Invoice    | `list_invoices`, `get_invoice`      |
| Account    | `list_accounts`, `get_account`      |
| Product    | `list_products`, `get_product`      |
| Vendor     | `list_vendors`, `get_vendor`        |
| Sales Tax  | `list_sales_taxes`, `get_sales_tax` |
| Constants  | `list_currencies`, `list_countries`, `list_account_types`, `list_account_subtypes` |

### Mutations

| Resource    | Methods                                              |
|-------------|------------------------------------------------------|
| Customer    | `create_customer`, `patch_customer`, `delete_customer` |
| Invoice     | `create_invoice`, `patch_invoice`, `delete_invoice`, `clone_invoice`, `approve_invoice`, `send_invoice`, `mark_invoice_sent` |
| Account     | `create_account`, `patch_account`, `archive_account` |
| Product     | `create_product`, `patch_product`, `archive_product` |
| Sales Tax   | `create_sales_tax`, `patch_sales_tax`, `archive_sales_tax` |
| Transaction | `create_money_transaction`, `create_money_transactions` |

## Examples

Set up a `.env` file with your Wave OAuth credentials:

```env
WAVE_CLIENT_ID=...
WAVE_CLIENT_SECRET=...
WAVE_ACCESS_TOKEN=...
WAVE_REFRESH_TOKEN=...
WAVE_REDIRECT_URI=http://localhost:3099/callback
WAVE_BUSINESS_ID=...
```

Then run any of the examples:

```bash
# List all businesses
cargo run --example list_businesses

# Create an invoice
cargo run --example create_invoice

# Profit & Loss report
cargo run --example profit_and_loss -- --start-date 2025-01-01 --end-date 2025-12-31
```

## Authentication

wave-api uses OAuth2. You'll need to [register an application](https://developer.waveapps.com/) with Wave to obtain a client ID and secret. See `oauth_flow.py` for a helper that runs the authorization code flow locally.

When an API call receives an `UNAUTHENTICATED` error, the client automatically refreshes the access token using the refresh token and retries the request once. You can provide an `on_token_refresh` callback to persist new tokens:

```rust
let client = WaveClient::with_oauth(OAuthConfig {
    // ...
    on_token_refresh: Some(Arc::new(|new_access, new_refresh| {
        println!("New tokens: {new_access}, {new_refresh}");
    })),
});
```

## Error Handling

All methods return `Result<T, WaveError>`. The error enum covers:

- `Http` — network / HTTP errors
- `Json` — serialization / deserialization failures
- `GraphQL` — errors returned by the Wave API
- `MutationFailed` — mutation `didSucceed: false` with `inputErrors`
- `Auth` — missing or invalid credentials
- `TokenRefresh` — refresh token flow failed

## License

MIT