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.

Or clone locally

git clone -b challenge/2026-09-06 https://github.com/niltonheck/typedrop.git