Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

12. Fold Reference

umari::prelude ships a handful of generic folds for the most common state-derivation shapes — “did this happen?”, “what’s the latest value?”, “how many times?”, “which of these two won?”. Reach for these first; only write a custom fold when none of them fit.

FoldStatePick when you ask…
EventFold<E>EventState<E>“Give me every occurrence of E
LatestEvent<E>Option<StoredEvent<E>>“What’s the most recent E?”
EventCounter<E>u64“How many E have happened?”
EventToggle<A, B>ToggleState<A, B>“Was the last event A or B?”
SingleEvent<E>— (an EventSet)Custom fold that only reads one event type

EventFold

Collects every occurrence of event type E into a Vec.

State: EventState<E>

#![allow(unused)]
fn main() {
.fold::<EventFold<ShopConnected>>()
.execute(|input, connected| {
    if connected.exists() {
        let first = &connected.events[0];
        // ...
    }
    Ok(emit![])
})
}
#![allow(unused)]
fn main() {
impl<E: Event> EventState<E> {
    pub events: Vec<StoredEvent<E>>;
    pub fn exists(&self) -> bool;
}
}

Use when: you need to inspect or aggregate over the full history. Avoid when: you only need the most recent value — LatestEvent is cheaper.

LatestEvent

Keeps only the most recent E. Each new event replaces the previous.

State: Option<StoredEvent<E>>

#![allow(unused)]
fn main() {
.fold::<LatestEvent<WarrantyPlanUpdated>>()
.execute(|input, latest| {
    if let Some(event) = latest {
        // event.data is the most recent WarrantyPlanUpdated
    }
    Ok(emit![])
})
}

Use when: you care about the current value — last Updated, last Connected, etc. Avoid when: you need the full history.

EventCounter

Counts occurrences of E without storing event data.

State: u64

#![allow(unused)]
fn main() {
.fold::<EventCounter<WarrantySold>>()
.execute(|input, sale_count| {
    anyhow::ensure!(
        sale_count < MAX_WARRANTIES,
        "shop has reached the maximum number of warranties"
    );
    Ok(emit![/* ... */])
})
}

Use when: you only need a count. Avoid when: you need to inspect individual events.

EventToggle

Tracks which of two opposing events occurred last. Designed for created/deleted, activated/deactivated, archived/unarchived pairs.

State: ToggleState<A, B>

#![allow(unused)]
fn main() {
.fold::<EventToggle<WarrantyPlanArchived, WarrantyPlanUnarchived>>()
.execute(|input, toggle| {
    if toggle.is_a() {
        // currently archived
    } else if toggle.is_b() {
        // currently unarchived
    } else {
        // neither has happened
    }
    Ok(emit![])
})
}
#![allow(unused)]
fn main() {
impl<A: Event, B: Event> ToggleState<A, B> {
    pub last: Option<ToggleSide<A, B>>;
    pub fn is_a(&self) -> bool;
    pub fn is_b(&self) -> bool;
    pub fn as_a(&self) -> Option<&StoredEvent<A>>;
    pub fn as_b(&self) -> Option<&StoredEvent<B>>;
}
}

Use when: paired opposing events — archived/unarchived, activated/deactivated, locked/unlocked. Constraint: A and B must share the same domain ID fields.

SingleEvent

Not a fold — an EventSet shorthand for custom folds that only read a single event type. Use it as type Events = SingleEvent<MyEvent>;.

#![allow(unused)]
fn main() {
#[derive(DomainIds, FromDomainIds)]
pub struct ShopExistsFold {
    #[domain_id]
    pub shop_id: u64,
}

impl Fold for ShopExistsFold {
    type Events = SingleEvent<ShopConnected>;
    type State = bool;

    fn apply(&self, exists: &mut bool, _event: StoredEvent<ShopConnected>) {
        *exists = true;
    }
}
}

Custom folds

When none of the built-ins fit, implement Fold yourself:

#![allow(unused)]
fn main() {
#[derive(DomainIds, FromDomainIds)]
pub struct MyFold {
    #[domain_id] pub shop_id: u64,
    #[from_domain_id(default)]
    pub custom_field: String,
}

#[derive(EventSet)]
pub enum MyFoldEvents {
    #[scope(shop_id)]
    EventA(EventA),
    EventB(EventB),
}

#[derive(Default)]
pub struct MyState {
    pub a_count: u64,
    pub b_count: u64,
    pub latest_value: Option<String>,
}

impl Fold for MyFold {
    type Events = MyFoldEvents;
    type State = MyState;

    fn apply(&self, state: &mut MyState, event: StoredEvent<MyFoldEvents>) {
        match event.data {
            MyFoldEvents::EventA(_) => state.a_count += 1,
            MyFoldEvents::EventB(ev) => {
                state.b_count += 1;
                state.latest_value = Some(ev.some_field);
            }
        }
    }
}
}

#[from_domain_id(default)]

Fields not annotated with #[domain_id] can use #[from_domain_id(default)] to get their default value during fold construction. The fold won’t try to bind these from domain IDs.

Fold composition limit

Commands support up to 12 folds in a single tuple. If you need more, compose multiple folds into a single fold by nesting state types, or split the command into multiple commands.