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, effect, reducer};
#[derive(Clone, Debug, Default)]
struct AppState {
user_count: i32,
order_count: i32,
}
// Define multiple event types
struct UserCreated { name: String }
struct OrderPlaced { amount: f64 }
// Create store with per-event reducers
let store = Engine::new(AppState::default())
.with_reducer(reducer::on::<UserCreated>().run(|state, _event| AppState {
user_count: state.user_count + 1,
..state
}))
.with_reducer(reducer::on::<OrderPlaced>().run(|state, _event| AppState {
order_count: state.order_count + 1,
..state
}))
.with_effect(effect::on::<UserCreated>().run(|event, ctx| async move {
println!("User created: {}", event.name);
Ok(())
}));
// Activate and emit events
let handle = store.activate();
handle.context.emit(UserCreated { name: "Alice".into() });
handle.context.emit(OrderPlaced { amount: 99.99 });
handle.settled().await?;
assert_eq!(store.state().user_count, 1);
assert_eq!(store.state().order_count, 1);