seesaw_core 0.7.5

A deterministic, event-driven coordination layer where machines decide, effects execute, and transactions define authority
Documentation

Seesaw

A Redux-style state machine with TypeId-based multi-event dispatch.

Named after the playground equipment that balances back and forth — representing the back-and-forth nature of state transitions.

Guarantees

  • Serial reduction: Reducers are executed serially under a write lock. No two events are ever reduced concurrently, even when emitted from multiple tasks.
  • Multi-event dispatch: Support for multiple event types via TypeId routing.
  • Effect system: Register handlers that react to events and can emit new events, spawn tracked tasks, and access shared dependencies.
  • Per-event reducers: Register reducers for specific event types.

Example

use seesaw::{Engine, on, fold};

#[derive(Clone, Debug, Default)]
struct AppState {
    user_count: i32,
    order_count: i32,
}

// Define event types (struct-per-event pattern)
#[derive(Clone)]
struct UserCreated { name: String }
#[derive(Clone)]
struct OrderPlaced { amount: f64 }
#[derive(Clone)]
struct UserWelcomed { name: String }

// Create engine with fold (reducers) and on (effects)
let engine = Engine::new()
    // Fold events into state
    .with_reducer(fold::<UserCreated>().into(|state, _| AppState {
        user_count: state.user_count + 1,
        ..state
    }))
    .with_reducer(fold::<OrderPlaced>().into(|state, _| AppState {
        order_count: state.order_count + 1,
        ..state
    }))
    // On event, then return next event
    .with_effect(on::<UserCreated>().then(|event, _ctx| async move {
        println!("User created: {}", event.name);
        Ok(UserWelcomed { name: event.name.clone() })
    }));

// Activate and dispatch events via run()
let handle = engine.activate(AppState::default());
handle.run(|_| Ok(UserCreated { name: "Alice".into() }))?;
handle.run(|_| Ok(OrderPlaced { amount: 99.99 }))?;
handle.settled().await?;