TypeDrop
2026-09-06 Challenge
2026-09-06
Hard
Typed Event Sourcing Engine with Snapshot Compaction & Projection Rebuilding
You're building the event-sourcing core for a collaborative document platform. Domain events arrive as a discriminated union, an append-only event log must be replayed into aggregate state via typed reducers, periodic snapshots compact history for fast rebuilds, and read-model projections subscribe to specific event subsets — all with the compiler enforcing every event shape, reducer signature, and projection contract.
Goals
- Define the full `DomainEvent` discriminated union and all five derived helper types (`EventKind`, `EventByKind<K>`, `EventPayload<K>`, `Snapshot<S>`, `Reducer<S,E>`) using only conditional types, mapped types, and utility types — no `any` or `as`.
- Implement `documentReducer` exhaustively handling all six event kinds, using a type-safe `never`-check to catch unhandled branches at compile time.
- Implement `replayFromSnapshot` and `compactLog` so that snapshotting and partial replay produce state identical to a full replay from event zero.
- Implement `runProjection` with a runtime type guard that narrows each `DomainEvent` to `EventByKind<K>` before passing it to `projection.handle`, then wire up `collaboratorProjection` as a concrete `Projection` instance.
challenge.ts
// Key types & main function signatures at a glance
type Role = "viewer" | "editor" | "admin";
type EventKind = DomainEvent["kind"]; // union of all .kind strings
type EventByKind<K extends EventKind> = // narrows to one member
Extract<DomainEvent, { kind: K }>;
type Reducer<S, E extends DomainEvent> =
(state: S | null, event: E) => S;
type Projection<TState, K extends EventKind> = {
eventKinds: readonly K[];
initialState: TState;
handle: (state: TState, event: EventByKind<K>) => TState;
};
// Replay a log (optionally from a snapshot) into aggregate state
function replayFromSnapshot<S>(
log: EventLog,
reducer: Reducer<S, DomainEvent>,
snapshot?: Snapshot<S>
): S { /* TODO */ }
// Run a typed read-model projection over an event log
function runProjection<TState, K extends EventKind>(
log: EventLog,
projection: Projection<TState, K>
): TState { /* TODO */ }
Hints (click to reveal)
Hints
- For `EventByKind<K>`, the built-in `Extract<Union, Shape>` utility type is your friend — no need to write a custom conditional type from scratch.
- In `runProjection`, cast-free narrowing is possible: check `(projection.eventKinds as readonly string[]).includes(event.kind)` inside a user-defined type guard function that returns `event is EventByKind<K>`.
- Make `documentReducer`'s `switch` exhaustive by adding a `default` branch that assigns `event` to a `never`-typed variable — the compiler will error if any `kind` is unhandled.
Useful resources
Or clone locally
git clone -b challenge/2026-09-06 https://github.com/niltonheck/typedrop.git