TypeDrop

2026-10-27 Challenge

2026-10-27 Medium

Typed Paginated API Client with Result Accumulation

You're building the data-fetching layer for an analytics dashboard that pulls reports from a paginated REST API. The client must walk pages automatically, accumulate results into typed summaries, and surface per-page errors without losing successfully fetched data.

Goals

  • Implement `PickRows<R, C>` as a distributive conditional type that filters rows by category.
  • Implement `SummaryDisplay` as a mapped type over `ReportCategory` producing a string per key.
  • Implement `buildCategorySummary` to aggregate all rows in a single pass, including zero-row categories.
  • Implement `fetchAllPages` to sequentially follow page cursors, accumulate rows, record errors, and stop pagination on failure.
challenge.ts
export type ReportCategory = "revenue" | "engagement" | "retention" | "churn";

export interface ReportRow {
  id: string;
  category: ReportCategory;
  value: number;
  label: string;
}

export type CategorySummary = Record<
  ReportCategory,
  { count: number; totalValue: number; avgValue: number }
>;

export interface FetchAllResult {
  rows: ReportRow[];
  summary: CategorySummary;
  errors: PageError[];
  pagesAttempted: number;
}

// Your two utility types:
export type PickRows<R extends { category: string }, C extends string> = never; // TODO A
export type SummaryDisplay = never; // TODO B

// Core function to implement:
export async function fetchAllPages(
  fetchPage: FetchPage,
  maxPages?: number
): Promise<FetchAllResult> { /* TODO */ throw new Error(); }
Hints (click to reveal)

Hints

  • For `PickRows`, a distributive conditional type distributes over unions automatically when the type parameter appears naked on the left side of `extends`.
  • For `SummaryDisplay`, use `{ [K in ReportCategory]: string }` rather than `Record<ReportCategory, string>` to satisfy the mapped-type requirement.
  • In `fetchAllPages`, a `while (cursor !== null && pagesAttempted < maxPages)` loop with sequential `await` is the cleanest pattern — break out of it when you receive a `PageError`.

Or clone locally

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