# nodos-store-client

A blocking Rust client for the [Nodos Store](https://store.nodos.dev) API.

## Installation

```toml
[dependencies]
nodos-store-client = "0.7"
```

By default the crate uses `rustls` for TLS. To use the platform native TLS instead:

```toml
nodos-store-client = { version = "0.7", default-features = false, features = ["native-tls"] }
```

## Usage

### Browsing packages (no authentication required)

```rust
use nodos_store_client::StoreClient;

let client = StoreClient::builder().build()?;

// List all packages
for pkg in client.list_packages()? {
    println!("{} ({})", pkg.name, pkg.package_type);
}

// Get releases for a specific package
let releases = client.get_releases("studio.plugin")?;
for release in releases {
    println!("{} — {:?}", release.version, release.tags);
}
```

### Authentication

Authentication uses the [OAuth 2.0 device authorization flow](https://www.rfc-editor.org/rfc/rfc8628).
The client does not print anything — your application receives the device start info and
decides how to present it to the user.

```rust
use nodos_store_client::{StoreClient, TokenStore};
use std::path::PathBuf;

let mut client = StoreClient::builder()
    .with_token_store(TokenStore::new(PathBuf::from("my-app")))
    .build()?;

// Step 1: start the flow and show the user where to go
let start = client.start_device_login()?;
println!("Open this URL to log in: {}", start.verification_uri_complete);
println!("Or visit {} and enter code: {}", start.verification_uri, start.user_code);

// Step 2: block until the user approves, then store the token
client.complete_device_login(&start)?;
println!("Logged in.");
```

The token is persisted to disk by `TokenStore` and loaded automatically on the next run.
The token file location is:

| Platform | Path |
|----------|------|
| Windows  | `%APPDATA%\<config_dir>\nodos-store\access-token.json` |
| Linux/macOS | `$XDG_CONFIG_HOME/<config_dir>/nodos-store/access-token.json` (falls back to `~/.config`) |

Where `<config_dir>` is the `PathBuf` passed to `TokenStore::new`.

To log out and delete the stored token:

```rust
client.logout()?;
```

### Token from environment

The client checks the following environment variable before using any stored token,
which is useful in CI/CD environments:

- `NODOS_STORE_ACCESS_TOKEN`

### Publishing a release

```rust
use nodos_store_client::{StoreClient, ApiVersion, PackageDependency};

let mut client = StoreClient::builder()
    .with_token("my-access-token")
    .build()?;

let artifact = std::fs::read("my-plugin.zip")?;

let release = client.publish_release(
    "studio.plugin",              // package name
    "Studio Plugin",              // display name
    "A compositing plugin",       // description
    "Plugin",                     // package type
    "Compositing",                // category
    "1.2.0",                      // version
    Some(&ApiVersion { major: 2, minor: Some(0), patch: Some(0) }),
    vec![PackageDependency { name: "studio.shared".into(), version: "1.0.0".into() }],
    vec!["stable".to_string()],   // tags
    "x86_64-windows",             // target platform
    artifact,
    PackageVisibility::Public,    // visibility at first publish
)?;

println!("Published release {}", release.version);
```

Supported target platforms are available via `nodos_store_client::SUPPORTED_TARGET_PLATFORMS`.

### Deleting releases

```rust
// Delete a specific version
client.delete_release("studio.plugin", Some("1.0.0"))?;

// Delete all releases for a package
client.delete_release("studio.plugin", None)?;
```

## Error handling

All methods return `nodos_store_client::Result<T>`, which is an alias for
`std::result::Result<T, nodos_store_client::Error>`. The `Error` enum covers
HTTP, authentication, parse, and IO failures, with distinct variants for common
cases like `NotFound`, `PermissionDenied`, `Conflict`, and `DeviceFlowExpired`.

```rust
use nodos_store_client::Error;

match client.get_releases("studio.plugin") {
    Ok(releases) => { /* ... */ }
    Err(Error::NotFound(_)) => eprintln!("Package not found"),
    Err(e) => eprintln!("Error: {e}"),
}
```

## TLS features

| Feature | Description |
|---------|-------------|
| `rustls-tls` (default) | Uses `rustls` — no system TLS dependency |
| `native-tls` | Uses the platform TLS library |
