TypeDrop

2026-05-02 Challenge

2026-05-02 Easy

Typed Event RSVP Aggregator

You're building the guest-management layer for an event-planning app. Raw RSVP payloads arrive as `unknown` from a public form endpoint; your aggregator must validate them, tally attendance per event, and return a strongly-typed summary — with zero `any`.

Goals

  • Implement `validateRsvp` to narrow `unknown` payloads into typed `Rsvp` values, returning the correct `RsvpValidationError` code for each failure mode.
  • Implement `buildEventSummary` to compute `attendingCount`, `declinedCount`, `maybeCount`, and `totalAttending` (primary guest + plusOnes) from a list of validated RSVPs.
  • Implement `aggregateRsvps` to validate all payloads, group valid RSVPs by `eventId`, produce one `EventSummary` per event, and collect all failures — never throwing.
  • Use only `readonly` properties, discriminated-union narrowing, and utility types — no `any`, `as`, or `!`.
challenge.ts
/** Possible responses a guest can submit. */
type RsvpStatus = "attending" | "declined" | "maybe";

/** A validated RSVP from a single guest. */
interface Rsvp {
  readonly guestName: string;
  readonly eventId:   string;
  readonly status:    RsvpStatus;
  readonly plusOnes:  number;   // ≥ 0; defaults to 0 if absent
}

type RsvpValidationError =
  | "MISSING_GUEST_NAME" | "MISSING_EVENT_ID"
  | "INVALID_STATUS"     | "INVALID_PLUS_ONES"
  | "NOT_AN_OBJECT";

type Result<T, E extends string = string> =
  | { readonly ok: true;  readonly value: T }
  | { readonly ok: false; readonly error: E };

// ── Functions you must implement ──────────────────────────────
declare function validateRsvp(payload: unknown): Result<Rsvp, RsvpValidationError>;
declare function buildEventSummary(eventId: string, rsvps: readonly Rsvp[]): EventSummary;
declare function aggregateRsvps(payloads: readonly unknown[]): AggregationReport;
Hints (click to reveal)

Hints

  • Use `typeof payload === 'object' && payload !== null` as your first guard before narrowing individual fields with `'guestName' in payload`.
  • A small helper like `const STATUSES: readonly RsvpStatus[] = [...]` plus `.includes()` lets you validate the status union without casting.
  • Build a `Record<string, Rsvp[]>` accumulator inside `aggregateRsvps` using a plain `for` loop or `Array.reduce` — then call `buildEventSummary` on each bucket.

Or clone locally

git clone -b challenge/2026-05-02 https://github.com/niltonheck/typedrop.git