Core concepts

This section covers the mechanics of Hauchiwa's "graph" architecture.

The Blueprint

The Blueprint is the architectural drawing board where you define your site. It is the central registry where you add tasks and configure loaders.

let mut config = Blueprint::<()>::new();

The generic parameter (<T>) allows you to pass a shared context (like a global configuration) to every task, though () is common for simple sites.

Tasks and handles

In Hauchiwa, everything is a Task. Tasks take input, process it, and produce output.

To wire tasks together, we use Handles. When you register a task (or a loader), you get a Handle back. This Handle acts as a token that represents the future output of that task.

The handle system

Hauchiwa strictly types these handles to ensure your graph is valid:

Wiring dependencies

You use .depends_on() to connect tasks. This is where the magic happens. The type system ensures that the data produced by the upstream task matches what the downstream task expects.

// 'pages' is a Many<Document> handle
let pages = config.load_documents::<Frontmatter>().source("*.md").register()?;

config.task()
    .depends_on(pages) // We pass the handle here
    .run(|ctx, pages| {
        // 'pages' is now resolved to the actual data (Tracker<Document>)
        Ok(())
    });

Loaders (input)

Loaders are special tasks that bridge the gap between the FileSystem and the Graph. They ingest files and turn them into typed data structures.

Common loaders include:

Output

To get data out of the graph and back onto the FileSystem (e.g., writing HTML files), your tasks return Output structs.

use hauchiwa::{Output, output::OutputData};

// Inside a task closure
let html = "<html>...</html>";

Ok(vec![Output {
    path: "index.html".into(),
    data: OutputData::Utf8(html.to_string()),
}])