import type { AggregateRoot } from "../libraries/aggregate.ts"; import type { Unknown } from "../types/common.ts"; import { EventFactory } from "./event-factory.ts"; import type { AnyEventStore } from "./event-store.ts"; /** * Make an event reducer that produces a aggregate instance from resolved * events. * * @param aggregate - Aggregate to instantiate and create an instance of. */ export function makeAggregateReducer< TEventFactory extends EventFactory, TAggregateRoot extends typeof AggregateRoot, >(store: AnyEventStore, aggregate: TAggregateRoot): Reducer> { return { from(snapshot: Unknown) { return aggregate.from(store, snapshot); }, reduce(events: TEventFactory["$events"][number]["$record"][], snapshot?: Unknown) { const instance = aggregate.from(store, snapshot); for (const event of events) { instance.with(event); } return instance; }, }; } /** * Make an event reducer that produces a state based on resolved events. * * @param foldFn - Method which handles the event reduction. * @param stateFn - Default state factory. */ export function makeReducer( foldFn: ReducerLeftFold, stateFn: ReducerState, ): Reducer { return { from(snapshot: TState) { return snapshot; }, reduce(events: TEventFactory["$events"][number]["$record"][], snapshot?: TState) { return events.reduce(foldFn, snapshot ?? (stateFn() as TState)); }, }; } export type Reducer< TEventFactory extends EventFactory = EventFactory, TState extends Record | AggregateRoot = any, > = { /** * Return result directly from a snapshot that does not have any subsequent * events to fold onto a state. * * @param snapshot - Snapshot of a reducer state. */ from(snapshot: Unknown): TState; /** * Take in a list of events, and return a state from the given events. * * @param events - Events to reduce. * @param snapshot - Initial snapshot state to apply to the reducer. */ reduce(events: TEventFactory["$events"][number]["$record"][], snapshot?: Unknown): TState; }; /** * Take an event, and fold it onto the given state. * * @param state - State to fold onto. * @param event - Event to fold from. * * @example * ```ts * const events = [...events]; * const state = events.reduce((state, event) => { * state.foo = event.data.foo; * return state; * }, { * foo: "" * }) * ``` */ export type ReducerLeftFold< TState extends Record = any, TEventFactory extends EventFactory = EventFactory, > = (state: TState, event: TEventFactory["$events"][number]["$record"]) => TState; export type ReducerState = () => TState; export type InferReducerState = TReducer extends Reducer ? TState : never;