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

Fold Reference

Both SDKs ship 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.

FoldRust stateTypeScript statePick when you ask…
EventFoldEventState<E>StoredEvent<E>[]“Give me every occurrence of E
LatestEventOption<StoredEvent<E>>{ value?: StoredEvent<E> }“What’s the most recent E?”
EventCounteru64{ count: bigint }“How many E have happened?”
EventToggleToggleState<A, B>{ last?: { side, event } }“Was the last event A or B?”
SingleEventan EventSetevents: [E]Custom fold reading one event type

In Rust you name the fold type (cmd.fold::<EventFold<E>>()); in TypeScript you call the helper and bind it inside the command’s folds map (EventFold(E)({ … })).

EventFold

Collects every occurrence of event type E.

State: EventState<E> ({ events: Vec<StoredEvent<E>>, fn exists() -> bool })

.fold::<EventFold<UserRegistered>>()
.execute(|input, registered| {
    if registered.exists() {
        let first = &registered.events[0];
        // ...
    }
    Ok(emit![])
})

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>>

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

Use when: you care about the current value, e.g. last Updated, last Created. Avoid when: you need the full history.

EventCounter

Counts occurrences of E without storing event data.

State: u64

.fold::<EventCounter<TaskCreated>>()
.execute(|input, task_count| {
    anyhow::ensure!(task_count < MAX_TASKS, "project has reached the maximum number of tasks");
    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. A and B must share the same domain ID fields.

State: ToggleState<A, B>

.fold::<EventToggle<ProjectArchived, ProjectUnarchived>>()
.execute(|input, toggle| {
    if toggle.is_a() {
        // currently archived
    } else if toggle.is_b() {
        // currently unarchived
    } else {
        // neither has happened
    }
    Ok(emit![])
})
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.

SingleEvent

For a custom fold that reads a single event type.

SingleEvent<E> is an EventSet shorthand; use it as type Events = SingleEvent<MyEvent>;:

#[derive(DomainIds, FromDomainIds)]
pub struct UserExistsFold {
    #[domain_id]
    pub user_id: u64,
}

impl Fold for UserExistsFold {
    type Events = SingleEvent<UserRegistered>;
    type State = bool;

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

Custom folds

When none of the built-ins fit, write the fold yourself.

#[derive(DomainIds, FromDomainIds)]
pub struct MyFold {
    #[domain_id] pub user_id: u64,
    #[from_domain_id(default)]
    pub custom_field: String,
}

#[derive(EventSet)]
pub enum MyFoldEvents {
    #[scope(user_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] get their default value during fold construction instead of being bound from domain IDs.

Fold composition limit

Rust commands support up to 12 folds in a single tuple; if you need more, compose folds by nesting state types or split the command. TypeScript commands use a named folds map with no fixed limit, but the same advice applies: keep each command focused.