TypeDrop
2026-03-05 Challenge
2026-03-05
Medium
Typed Paginated API Client with Result Chaining
You're building the data-fetching layer for an admin dashboard that queries a paginated REST API. Each endpoint returns a different resource shape, and the client must transparently walk pages, accumulate results, and surface typed errors — all without a single `any`.
Goals
- Define and use a discriminated-union Result<T,E> type with Ok and Err variants throughout all functions.
- Implement fetchAllPages to walk paginated cursors sequentially, accumulating items and short-circuiting on the first error.
- Write runtime validators (validateUser, validateAuditLog, validatePage) that narrow unknown to typed structs using only typeof/in guards — no type assertions.
- Implement mapResult, flatMapResult, and pipeResults to enable safe, composable result chaining.
challenge.ts
// Key types
export type Ok<T> = { readonly kind: "ok"; readonly value: T };
export type Err<E> = { readonly kind: "err"; readonly error: E };
export type Result<T, E> = Ok<T> | Err<E>;
export type ApiError =
| { readonly kind: "network"; readonly message: string }
| { readonly kind: "not_found"; readonly resource: string }
| { readonly kind: "parse"; readonly raw: string };
export interface Page<T> {
readonly items: T[];
readonly nextCursor: string | null;
readonly total: number;
}
export type PageFetcher<T> =
(cursor: string | null) => Promise<Result<Page<T>, ApiError>>;
// Core functions you must implement:
export async function fetchAllPages<T>(
fetcher: PageFetcher<T>
): Promise<Result<T[], ApiError>> { /* TODO */ }
export function mapResult<T, U, E>(
result: Result<T, E>, fn: (value: T) => U
): Result<U, E> { /* TODO */ }
export function groupById<T extends { id: string }>(
items: T[]
): ReadonlyMap<string, T> { /* TODO */ }
Hints (click to reveal)
Hints
- For validateUser, check typeof on each field individually before constructing the User — the compiler will track the narrowed type for you.
- fetchAllPages should use a while loop driven by nextCursor: start with null, replace it with the cursor from each successful page until it's null again.
- pipeResults can be solved in a single pass with Array.reduce — accumulate values into an Ok([]) and short-circuit to Err on the first Err you encounter.
Useful resources
Or clone locally
git clone -b challenge/2026-03-05 https://github.com/niltonheck/typedrop.git