TypeDrop

2026-05-10 Challenge

2026-05-10 Easy

Typed User Session Aggregator

You're building the analytics layer for a SaaS dashboard. Raw session events arrive as `unknown` from a client-side tracking SDK; your engine must validate them, group them by user, and return a strongly-typed per-user session summary — with zero `any`.

Goals

  • Implement `validateSessionEvent` to narrow `unknown` into a typed `SessionEvent`, returning a discriminated-union `Result<SessionEvent>`.
  • Implement `aggregateSessions` to validate every raw event, collect errors, and group valid events by `userId`.
  • Compute per-user statistics — total sessions, total/average duration, page-visit counts, and most-visited page — using only typed accumulators.
  • Return a `SessionReport` with valid/invalid counts, sorted summaries, and all collected error messages.
challenge.ts
/** Pages a user can visit inside the app. */
type PageName = "home" | "dashboard" | "settings" | "billing" | "logout";

/** Discriminated-union result type. */
type Result<T> = { ok: true; value: T } | { ok: false; error: string };

/** Per-page visit counts. */
type PageVisitMap = Record<PageName, number>;

/** Per-user aggregated summary. */
interface UserSessionSummary {
  userId: string;
  totalSessions: number;
  totalDurationSeconds: number;
  avgDurationSeconds: number;   // rounded to 2 decimal places
  pageVisits: PageVisitMap;
  mostVisitedPage: PageName;
}

// Validate a single raw event from the tracking SDK
declare function validateSessionEvent(raw: unknown): Result<SessionEvent>;

// Aggregate an array of raw events into a full SessionReport
declare function aggregateSessions(rawEvents: unknown[]): SessionReport;
Hints (click to reveal)

Hints

  • Use `typeof raw === 'object' && raw !== null` before casting with `in` operator checks — no `as` needed.
  • Initialise `PageVisitMap` by spreading `VALID_PAGES` into `Object.fromEntries` so every `PageName` key starts at `0` before you start counting.
  • To find `mostVisitedPage`, sort the `PageName` entries of `pageVisits` first alphabetically, then pick the one with the maximum count — sorting first handles the tie-break for free.

Or clone locally

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