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.
Useful resources
Or clone locally
git clone -b challenge/2026-05-10 https://github.com/niltonheck/typedrop.git