TypeDrop

2026-06-20 Challenge

2026-06-20 Hard

Typed Event Sourcing Engine with Projection & Snapshot

You're building the event-sourcing backbone for a collaborative document editor. Raw domain events arrive as `unknown` from a Kafka consumer; your engine must validate them, apply them to a strongly-typed aggregate, maintain projections via a registry of typed reducers, and produce versioned snapshots — with zero `any`.

Goals

  • Define branded types for AggregateId, EventId, and Version that are mutually incompatible at compile time.
  • Build a type-safe ProjectionRegistry using mapped types and Extract<> so each reducer receives the exact DomainEvent variant — not the full union.
  • Implement parseRawEvent to safely narrow unknown input to a typed DomainEvent, returning a Result<DomainEvent, ValidationError> with zero type assertions.
  • Wire everything together in processRawEventBatch: collect parse errors without halting, replay valid events sequentially, and return a fully-typed BatchResult including an optional snapshot.
challenge.ts

// Key types at a glance:
type AggregateId = string & { readonly __brand: unique symbol };
type Version     = number & { readonly __brand: unique symbol };

type DomainEvent =
  | { type: "DocumentCreated";   eventId: EventId; aggregateId: AggregateId; version: Version; occurredAt: string; title: string; authorId: string }
  | { type: "ContentUpdated";    eventId: EventId; aggregateId: AggregateId; version: Version; occurredAt: string; content: string; wordCount: number }
  | { type: "CollaboratorAdded"; eventId: EventId; aggregateId: AggregateId; version: Version; occurredAt: string; collaboratorId: string; role: "viewer" | "editor" }
  | { type: "DocumentArchived";  eventId: EventId; aggregateId: AggregateId; version: Version; occurredAt: string; reason: string };

type ProjectionRegistry<S> = {
  [K in DomainEvent["type"]]: (state: S, event: Extract<DomainEvent, { type: K }>) => S;
};

// Main pipeline entry point:
function processRawEventBatch(
  rawEvents: unknown[],
  initial: DocumentState,
  registry: ProjectionRegistry<DocumentState>
): BatchResult { /* ... */ }
Hints (click to reveal)

Hints

  • For ProjectionRegistry, `[K in DomainEvent["type"]]: (state: S, event: Extract<DomainEvent, { type: K }>) => S` lets TypeScript infer the exact variant in each reducer body.
  • When narrowing `unknown` in parseRawEvent, use `typeof` and `in` operator checks — never cast. A helper like `isNonNullObject(v: unknown): v is Record<string, unknown>` keeps the code clean.
  • Branded types need a `unique symbol` per brand; declare the symbol inside the type literal itself so each brand is truly distinct even without an exported symbol.

Or clone locally

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