a3s-flow 0.4.1

Durable workflow engine and Rust SDK for A3S
Documentation
use a3s_flow::{
    FlowEngine, FlowError, FlowRuntime, LocalFileEventStore, RuntimeCommand, StepInvocation,
    WorkflowInvocation, WorkflowRunStatus, WorkflowSpec,
};
use async_trait::async_trait;
use serde_json::json;
use std::sync::Arc;

struct GreetingRuntime;

#[async_trait]
impl FlowRuntime for GreetingRuntime {
    async fn run_workflow(
        &self,
        invocation: WorkflowInvocation,
    ) -> a3s_flow::Result<RuntimeCommand> {
        let ctx = invocation.context();
        if let Some(greeting) = ctx.step_output("greet") {
            return Ok(ctx.complete(greeting.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}"))),
        }
    }
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> a3s_flow::Result<()> {
    let root = std::env::temp_dir().join(format!(
        "a3s-flow-local-file-example-{}",
        std::process::id()
    ));
    let _ = tokio::fs::remove_dir_all(&root).await;

    let runtime = Arc::new(GreetingRuntime);
    let store = Arc::new(LocalFileEventStore::new(&root));
    let engine = FlowEngine::new(store.clone(), runtime.clone());
    let spec = WorkflowSpec::rust_embedded("examples.local-file", "0.1.0", "examples", "main");

    let run_id = engine
        .start_with_id("local-file-demo", spec.clone(), json!({ "name": "Ada" }))
        .await?;
    let first_snapshot = engine.snapshot(&run_id).await?;
    assert_eq!(first_snapshot.status, WorkflowRunStatus::Completed);

    let rebuilt_engine = FlowEngine::new(Arc::new(LocalFileEventStore::new(&root)), runtime);
    let rebuilt_snapshot = rebuilt_engine.snapshot("local-file-demo").await?;
    let replayed_run_id = rebuilt_engine
        .start_with_id("local-file-demo", spec, json!({ "name": "Ada" }))
        .await?;
    let history = rebuilt_engine.history("local-file-demo").await?;

    println!("event_store={}", root.display());
    println!("run_id={replayed_run_id}");
    println!("status={:?}", rebuilt_snapshot.status);
    println!("history_events={}", history.len());
    println!(
        "output={}",
        serde_json::to_string_pretty(&rebuilt_snapshot.output).unwrap()
    );

    let _ = tokio::fs::remove_dir_all(&root).await;
    Ok(())
}