# 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:
| [`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).