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.
| Fold | Rust state | TypeScript state | Pick when you ask… |
|---|---|---|---|
EventFold | EventState<E> | StoredEvent<E>[] | “Give me every occurrence of E” |
LatestEvent | Option<StoredEvent<E>> | { value?: StoredEvent<E> } | “What’s the most recent E?” |
EventCounter | u64 | { count: bigint } | “How many E have happened?” |
EventToggle | ToggleState<A, B> | { last?: { side, event } } | “Was the last event A or B?” |
SingleEvent | an EventSet | events: [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 = ®istered.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.