a3s-flow 0.4.0

Durable workflow engine and Rust SDK for A3S
Documentation
#[cfg(feature = "postgres")]
use a3s_flow::{
    FlowEngine, FlowError, FlowRuntime, PostgresEventStore, RuntimeCommand, StepInvocation,
    WorkflowInvocation, WorkflowSpec,
};
#[cfg(feature = "postgres")]
use async_trait::async_trait;
#[cfg(feature = "postgres")]
use serde_json::json;
#[cfg(feature = "postgres")]
use std::sync::Arc;
#[cfg(feature = "postgres")]
use uuid::Uuid;

#[cfg(feature = "postgres")]
struct GreetingRuntime;

#[cfg(feature = "postgres")]
#[async_trait]
impl FlowRuntime for GreetingRuntime {
    async fn run_workflow(
        &self,
        invocation: WorkflowInvocation,
    ) -> a3s_flow::Result<RuntimeCommand> {
        let ctx = invocation.context();
        if let Some(output) = ctx.step_output("greet") {
            return Ok(ctx.complete(output.clone()));
        }

        Ok(ctx.schedule_step(
            "greet",
            "greet_user",
            json!({ "name": ctx.input()["name"] }),
        ))
    }

    async fn run_step(&self, invocation: StepInvocation) -> a3s_flow::Result<serde_json::Value> {
        match invocation.step_name.as_str() {
            "greet_user" => Ok(json!({
                "message": format!("hello {}", invocation.input["name"].as_str().unwrap_or("unknown")),
            })),
            step => Err(FlowError::Runtime(format!("unknown step {step}"))),
        }
    }
}

#[cfg(feature = "postgres")]
#[tokio::main(flavor = "current_thread")]
async fn main() -> a3s_flow::Result<()> {
    let Some(url) = std::env::var("A3S_FLOW_POSTGRES_URL")
        .ok()
        .filter(|url| !url.trim().is_empty())
    else {
        println!("set A3S_FLOW_POSTGRES_URL=postgres://user:pass@host:5432/db to run it");
        return Ok(());
    };

    let run_id = format!("postgres-greeting-{}", Uuid::new_v4());
    let spec = WorkflowSpec::rust_embedded("examples.postgres", "0.1.0", "examples", "main");
    {
        let store = Arc::new(PostgresEventStore::connect(&url).await?);
        let engine = FlowEngine::new(store, Arc::new(GreetingRuntime));
        engine
            .start_with_id(&run_id, spec.clone(), json!({ "name": "Ada" }))
            .await?;
    }

    let store = Arc::new(PostgresEventStore::connect(&url).await?);
    let engine = FlowEngine::new(store, Arc::new(GreetingRuntime));
    let snapshot = engine.snapshot(&run_id).await?;

    println!("run_id={}", snapshot.run_id);
    println!("status={:?}", snapshot.status);
    println!(
        "output={}",
        serde_json::to_string_pretty(&snapshot.output).unwrap()
    );

    Ok(())
}

#[cfg(not(feature = "postgres"))]
fn main() {
    println!("postgres feature not enabled; run with:");
    println!("cargo run --example postgres_durability --features postgres");
}