sark-core 0.23.0

Fiber-free request, route, dispatch, and lifecycle core for Sark
Documentation
# sark

HTTP, WebSocket, and gRPC for Dope.

Requires Rust 1.98. Production applications must use `panic = "abort"` in
their workspace's release profile.

## Run an example

From this workspace, start the download server:

```sh
cargo +stable run --release -p sark-fiber --example download
```

In another terminal:

```sh
curl --fail http://127.0.0.1:8080/download/3
```

The response is three `chunk` lines. Append `-- --bind ADDRESS` to change the listener address.

| Example | Run target | Usage |
| --- | --- | --- |
| [Download]fiber/examples/download.rs | `-p sark-fiber --example download` | Iterator streaming; `--bind` defaults to `127.0.0.1:8080` |
| [Redis]fiber/examples/redis.rs | `-p sark-fiber --example redis` | `GET /` increments a counter; `POST /reset` deletes it |
| [Leaderboard]fiber/examples/leaderboard.rs | `-p sark-fiber --example leaderboard` | JSON input and Redis sorted sets |
| [Postgres]fiber/examples/pg.rs | `-p sark-fiber --example pg` | Queries and transactions |
| [HTTP/2]h2/examples/server.rs | `-p sark-h2 --example server` | Fixed responses; `--bind` defaults to `127.0.0.1:18080` |
| [Deadlines]core/examples/deadline.rs | `-p sark-core --example deadline` | Cancellation and rescheduling |

Use each target with the same `cargo +stable run --release` command.
Redis examples require an existing Redis server (`--redis-addr`, default
`127.0.0.1:6379`). The Postgres example requires a database matching its `User`
model and accepts `--pg-addr`, `--pg-user`, `--pg-password`, and `--pg-database`.
See [input defaults](fiber/examples/input/mod.rs). The examples do not create or seed databases.

## Handlers and application state

Declare request fields with `#[sark::request]`, implement a named function with
`#[sark::handler]`, and register it with `sark::define_route!`. A handler only
needs a request parameter when it consumes request data. State-only handlers
take the installed provider `&C` directly; they need no empty request type.

For example, the Postgres list handler is:

```rust
#[sark::handler]
async fn list_users<C>(pg: &C) -> Result<Response<Rows<C::Batch>>, body::Error<'_>>
where
    C: Client<'_, PG_CONNECTOR_ID, Schema = Db>,
{
    let users = User::page(pg.query(), 0, USER_PAGE_CAPACITY).await?;
    Ok(body::response(users.validate()?)?)
}
```

See [pg.rs](fiber/examples/pg.rs) and its [response encoder](fiber/examples/body/mod.rs).
`C::Batch` retains the query result through encoding. Application state need not
be `Copy`; it must remain inside its client scope until the session finishes.
Each route sets its body policy and task capacity; `sark_fiber::Config` sets
the application's task limit.

Transactions:

```rust
let tx = pg.transaction().begin().await?;
let outcome = User::rename(tx.query(), id.get(), name).await;
tx.finish(outcome).await?;
```

`finish` commits a successful operation or rolls back a failed one. Its typed
failure preserves both operation and rollback errors when both fail. The
example borrows `name` from the request across the operation.

## Server APIs

Generated H1 applications use `App::serve` or `App::serve_with` for an explicit
wire binding. Keep pinned state, the application, provider, and completion
storage alive until the session finishes. Workers own independent applications;
listener, upstream pool, and task capacities are separate settings.

Use `sark_h1::server::listener::Config::tcp(bind, capacity, head_timeout)` with
a validated `conn::Capacity`, or `Config::new` with a prepared Dope listener
for custom socket and egress settings.

Iterator responses use `sark_fiber::fiber::IntoStream`, as in the
[download example](fiber/examples/download.rs). Asynchronous producers implement
`dope_fiber::abi::Stream`.

`define_router!` creates a synchronous H1 router. `define_task_route!` creates
H2/H3 task applications using `sark-fiber`. H2 function handlers use
`sark_h2::server::Server::from_fn`, as in the [HTTP/2 example](h2/examples/server.rs).

JSON, WebSocket, and gRPC applications depend on `sark-json`, `sark-ws`, and
`sark-grpc` respectively. JSON uses `#[sark_json::json]`; gRPC uses Prost messages
and the `sark-grpc-build` service generator.

## HTTP/3 connection lifetime

For HTTP/3, choose positive `Duration` values for `max_age` and `drain_timeout`
when binding an existing `sark_h3::manifold::server::startup::Plan`:

```rust
let lifetime = sark_core::life::session::Config::try_new(max_age, drain_timeout)?;
let endpoint = plan.try_with::<dope_quic::runtime::mux::app::Silent, _, _, _, _, _>(
    router, observer, lifetime, deadline, sark_h3::manifold::server::Tasks::new(), driver,
)?;
```

Age starts when the connection is established. Expiry begins graceful draining
and rejects new requests; drain expiry closes the connection. Request deadlines
end when their request retires. QUIC idle timeout can be refreshed by traffic,
so neither bounds an active connection's lifetime. Pass
`sark_core::life::session::Disabled` instead of `lifetime` only when finite
connection rotation is not required.

© 2026 inkyu