flaron-sdk 1.1.0

Official Rust SDK for writing Flaron edge flares - WebAssembly modules that run on the Flaron CDN edge runtime.
Documentation
# flaron-sdk

The official Rust SDK for building **flares** - WebAssembly functions that run on the [Flaron](https://flaron.dev) CDN edge.

A flare is a small Wasm module deployed to every Flaron edge node worldwide. The host runtime invokes your `handle_request` export when a request lands at the nearest edge, your code runs in a sandboxed runtime with single-digit-millisecond cold starts, and the response is shipped back to the client without ever touching your origin.

```rust
use flaron_sdk::{request, response, FlareAction};

flaron_sdk::handle_request!(my_flare);

fn my_flare() -> FlareAction {
    let body = format!("hello from {} {}", request::method(), request::url());
    response::set_status(200);
    response::set_header("content-type", "text/plain");
    response::set_body_str(&body);
    FlareAction::Respond
}
```

## Installation

Add the SDK to your `Cargo.toml`:

```toml
[package]
name = "my-flare"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
flaron-sdk = "0.1"

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true
```

You will need the `wasm32-unknown-unknown` target installed:

```sh
rustup target add wasm32-unknown-unknown
```

Then build your flare:

```sh
cargo build --release --target wasm32-unknown-unknown
```

The resulting `target/wasm32-unknown-unknown/release/my_flare.wasm` is what you upload to Flaron.

## Quick start: HTTP echo flare

Create a new library crate:

```sh
cargo new --lib my-flare
cd my-flare
```

Set the crate type to `cdylib` and add the SDK as in the [Installation](#installation) section above.

Replace `src/lib.rs` with:

```rust
use flaron_sdk::{logging, request, response, FlareAction};

flaron_sdk::handle_request!(my_flare);

fn my_flare() -> FlareAction {
    let method = request::method();
    let url = request::url();
    logging::info(&format!("{} {}", method, url));

    let body = format!(
        "method: {}\nurl: {}\nuser-agent: {}\n",
        method,
        url,
        request::header("user-agent").unwrap_or_default(),
    );

    response::set_status(200);
    response::set_header("content-type", "text/plain; charset=utf-8");
    response::set_body_str(&body);

    FlareAction::Respond
}
```

The `handle_request!` macro takes the name of your handler function and
expands to the `alloc` and `handle_request` exports the host runtime
requires. The macro resets the bump arena on every invocation and converts
your returned [`FlareAction`] into the `i64` the host expects.

For WebSocket flares the equivalent macro is `ws_handlers!`:

```rust
use flaron_sdk::{logging, ws};

flaron_sdk::ws_handlers!(on_open, on_message, on_close);

fn on_open() {
    let _ = ws::send_text(&format!("welcome {}", ws::conn_id()));
}

fn on_message() {
    let payload = ws::event_text();
    let _ = ws::send_text(&format!("echo: {}", payload));
}

fn on_close() {
    logging::info(&format!("ws close code={}", ws::close_code()));
}
```

Pass the three handler names in `open, message, close` order. The macro
exports `ws_open` / `ws_message` / `ws_close` and resets the arena before
each call.

Use `handle_request!` *or* `ws_handlers!` per crate, never both - they
each define the `alloc` export the host runtime requires.

Build and deploy:

```sh
cargo build --release --target wasm32-unknown-unknown
flaronctl deploy target/wasm32-unknown-unknown/release/my_flare.wasm \
    --domain example.com --route '/*'
```

Curl the edge to see it run:

```sh
curl https://example.com/hello
```

## Features

The SDK exposes the full Flaron edge runtime through ergonomic Rust modules. Every operation runs in the host's native code path - no allocator, no async runtime, no crypto crate gets pulled into your `.wasm`.

### Request and response

- **`request`** - read inbound `method`, `url`, individual `header(name)`, and request `body` bytes.
- **`response`** - write `set_status`, `set_header(k, v)`, `set_body(bytes)`, `set_body_str(s)`.

### Outbound HTTP - `beam`

Make HTTP calls from the edge to your origin or third-party APIs. Supports method, headers, body, and returns status, headers, and response body.

```rust
use flaron_sdk::beam::{self, FetchOptions};

let resp = beam::fetch(
    "https://api.example.com/users/42",
    FetchOptions::new()
        .with_header("authorization", "Bearer ...")
        .with_header("accept", "application/json"),
)?;
```

### Edge KV stores

- **`spark`** - per-site key/value with TTL, persisted to local disk on each edge node. Best for caching, session state, and rate-limit counters local to one edge.
- **`plasma`** - cross-edge CRDT key/value replicated via gossip. Best for state that must be visible from any edge: PN counters, presence, leaderboards, feature flags.

```rust
use flaron_sdk::{spark, plasma};

spark::set("session:abc", b"data", 3600)?;     // expires in 1 hour
let session = spark::get("session:abc");

plasma::increment("page_views:home", 1);       // CRDT, gossiped to all edges
```

### Secrets - `secrets`

Read domain-scoped secrets that have been allowlisted for this flare. Secret values are gated by the flare's `allowed_secrets` config - anything not on the list returns `None`.

```rust
let api_key = flaron_sdk::secrets::get("third-party-api-key");
```

For HMAC and JWT signing, prefer the `crypto` helpers - they reference secrets by name without ever exposing the raw bytes to Wasm.

### Cryptography - `crypto`

Hash, HMAC, AES-GCM encrypt/decrypt, JWT signing, and CSPRNG random bytes. All operations run in the host's native crypto stack so they are constant-time and dramatically faster than a pure-Wasm crypto build.

```rust
use std::collections::HashMap;
use flaron_sdk::crypto;

let digest = crypto::hash("sha256", "hello world");
let mac = crypto::hmac("webhook-signing-key", "payload");

let mut claims = HashMap::new();
claims.insert("sub".to_string(), "user-42".to_string());
let jwt = crypto::sign_jwt("HS256", "session-key", &claims);

let entropy = crypto::random_bytes(32)?;
```

### Encoding helpers - `encoding`

Zero-dep wrappers for `base64`, `hex`, and URL percent-encoding. Calling the host avoids pulling encoding crates into your Wasm binary.

### ID generators - `id`

UUID v4, UUID v7 (time-ordered), ULID, KSUID, Nanoid, and Snowflake. All generated by the host's native crypto + clock so IDs are collision-resistant across edges with no entropy crate in your binary.

### Time - `time`

Timestamps in any format you need: unix seconds, milliseconds, nanoseconds, RFC3339, HTTP date, ISO8601.

### Logging - `logging`

`info`, `warn`, and `error` surfaced via the edge node's structured log stream - visible in the Flaron dashboard and forwarded to your log sink.

### WebSockets - `ws`

Receive open / message / close events and `send` / `close` from the flare. The same module exposes `event_type`, `event_data`, `event_text`, and `close_code` for the inbound side.

## Memory model

Each invocation starts with a fresh **256 KiB bump arena**. The host writes return data into the arena via your exported `alloc` function. The `handle_request!` and `ws_handlers!` macros include the `alloc` export and reset the arena on every invocation - you never need to free anything.

This is why most SDK functions return owned `String` or `Vec<u8>` values: they are read out of the arena and copied into the linear-memory heap before the arena is reset on the next call.

If you need to define the host exports yourself (custom `handle_request` shape, multiple entry points, etc.) call `flaron_sdk::export_alloc!()` and `flaron_sdk::reset_arena()` directly instead of using the macros.

## Examples

The [`examples/`](./examples) directory contains seven complete flares you can build today:

| Example | What it shows |
|---|---|
| [`hello`]./examples/hello | Minimal HTTP responder |
| [`spark-counter`]./examples/spark-counter | Per-edge KV with TTL |
| [`plasma-counter`]./examples/plasma-counter | Cross-edge CRDT counter |
| [`secret-jwt`]./examples/secret-jwt | Sign a JWT with a domain secret |
| [`websocket-echo`]./examples/websocket-echo | WebSocket open / message / close |
| [`beam-fetch`]./examples/beam-fetch | Outbound HTTP from the edge |
| [`edge-ops`]./examples/edge-ops | Crypto, encoding, ID, and timestamp showcase |

Build them all from the workspace root:

```sh
cargo build --release --target wasm32-unknown-unknown
```

Each example is a workspace member - its `.wasm` lands in `target/wasm32-unknown-unknown/release/<example_name>.wasm`.

## Documentation

- **Flaron docs:** [https://flaron.dev]https://flaron.dev
- **Repository:** [https://github.com/flaron-cdn/flare-rs]https://github.com/flaron-cdn/flare-rs

## License

MIT - see [LICENSE](./LICENSE).