forge-jobs-api 0.2.0

Axum HTTP transport for forge-jobs. Pure async handlers over the storage trait surface (shared with in-process IPC bindings) + JSON DTOs + a reference jobs-server binary.
Documentation

forge-jobs-api

crates.io docs.rs license

HTTP transport for forge-jobs — Axum routes over the same handler bodies the in-process Tauri / desktop binding would call. Lets ops poke a deployed queue without an in-process Rust binding and gives sidecar tooling a stable JSON contract.

Install

[dependencies]
forge-jobs     = "0.1"
forge-jobs-api = "0.1"

# For Postgres-backed deploys:
forge-jobs     = { version = "0.1", features = ["postgres"] }
forge-jobs-api = { version = "0.1", features = ["postgres"] }

Use

The crate exposes:

  • forge_jobs_api::router() — Axum Router you mount under your service. Routes are versioned-flat (no /v1/ prefix today; the internal API is still settling).
  • forge_jobs_api::handlers::* — pure async functions over &Storage. Same bodies the Tauri plugin's IPC commands call, so the two transports can't drift apart. Test against an in-memory SQLite — no router or HTTP container needed.
  • forge_jobs_api::dto::* — request/response shapes shared between HTTP and IPC consumers.
  • forge_jobs_api::Error — handler error type. Implements axum::response::IntoResponse so each variant maps to a sensible HTTP status code (400 / 404 / 409 / 429 / 500).

⚠️ Security — the routes are unauthenticated

router() returns an Axum Router with no auth, no rate limiting, no body-size limit, no CORS. Some routes mutate state (POST /queue/:name/backoff). You MUST either:

  1. Bind to loopback (127.0.0.1) so only same-host processes can hit it, or
  2. Mount behind your own middleware that enforces auth before the request reaches router():
    use axum::Router;
    let app: Router = Router::new()
        .nest("/api/queue", forge_jobs_api::router(storage))
        .layer(my_auth_middleware);
    

A future minor release will add an optional auth feature with a pluggable token check; today, that's on you.

Wiring it up

use std::sync::Arc;
use forge_jobs::storage::{DatabaseConfig, QueuePaths};
use forge_jobs::{HandlerRegistry, QueueRuntime};
use forge_jobs_api::router;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let paths = /* your QueuePaths impl */;
    let storage = DatabaseConfig::load(&paths)?.open_storage(&paths).await?;

    // Spawn workers (queue background).
    let runtime = QueueRuntime::new(storage.clone(), HandlerRegistry::new(), Arc::new(forge_jobs::DefaultRouter));
    runtime.ensure_queue("default", 4).await?;
    let _handle = runtime.start().await?;

    // Mount the HTTP API. Bound to loopback because routes are
    // unauthenticated — see the Security section above before
    // changing to 0.0.0.0.
    let app = router(storage);
    let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

jobs-server binary

The crate also ships a small reference binary:

# Default: SQLite via $JOBS_DATA_DIR / $JOBS_CONFIG_DIR env vars.
cargo run -p forge-jobs-api --bin jobs-server

# Postgres:
cargo run -p forge-jobs-api --bin jobs-server --features postgres

Bind address and worker count are env-configured — see src/bin/jobs-server.rs.

Routes

Method Path Description
GET /queue/overview All queues' status counts + live workers + retention settings
POST /queue/:name/backoff Set per-queue exponential backoff curve
GET /storage/info Backend identifier + boot-time facts
GET /metrics Prometheus exposition (queue depth, latency percentiles)

More routes land as the in-process IPC surface formalizes. The Tauri-plugin's commands and these handlers share the same DTOs, so divergence is structurally impossible.

Status

0.1 — tracks forge-jobs major version. The handler bodies are stable; route shapes may evolve before 1.0 as we tighten the externally-facing contract.

License

Dual-licensed under either of

at your option. Contributions intentionally submitted for inclusion in this crate shall be dual-licensed as above, without any additional terms or conditions.