TypeDrop
2026-07-22 Challenge
2026-07-22
Medium
Typed Paginated API Client with Result Aggregation
You're building the data-fetching layer for an analytics dashboard. The backend exposes a cursor-based paginated endpoint, and your client must walk every page, accumulate records, handle per-page errors gracefully, and surface a fully-typed aggregated result — all without losing type information or resorting to `any`.
Goals
- Implement the `ok` and `err` constructors that produce correctly-typed discriminated union values.
- Implement `fetchAllPages` to walk a cursor chain sequentially, collecting items and errors into an `AggregatedResult<T, E>`.
- Implement `pluckField` so its return type is inferred as `Array<T[K]>` using a `keyof` constraint and indexed access type.
- Implement `mapFetchPage` to wrap a `FetchPage<A, E>` and produce a `FetchPage<B, E>` by transforming each page's items, passing errors through unchanged.
challenge.ts
// Core types you'll be working with:
interface Page<T> {
items: T[];
nextCursor: string | null; // null = last page
totalCount: number;
}
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E> = Ok<T> | Err<E>;
type FetchPage<T, E> = (cursor: string | undefined) => Promise<Result<Page<T>, E>>;
interface AggregatedResult<T, E> {
items: T[];
totalCount: number;
pagesFetched: number;
errors: E[];
}
// Main function to implement:
async function fetchAllPages<T, E>(
fetchPage: FetchPage<T, E>
): Promise<AggregatedResult<T, E>> { /* TODO */ }
// Bonus utilities:
function pluckField<T, E, K extends keyof T>(result: AggregatedResult<T, E>, key: K): Array<T[K]> { /* TODO */ }
function mapFetchPage<A, B, E>(fetchPage: FetchPage<A, E>, transform: (item: A) => B): FetchPage<B, E> { /* TODO */ }
Hints (click to reveal)
Hints
- For `fetchAllPages`, a `while (cursor !== null)` loop with `cursor` typed as `string | undefined | null` lets you distinguish 'first call' from 'done' naturally.
- In `pluckField`, the constraint `K extends keyof T` is all you need — TypeScript will infer `T[K]` as the element type of the returned array without any casting.
- `mapFetchPage` should return an async arrow function that calls the inner `fetchPage`, then checks `result.ok` to decide whether to map items or forward the `Err` unchanged — discriminated union narrowing does the heavy lifting.
Useful resources
Or clone locally
git clone -b challenge/2026-07-22 https://github.com/niltonheck/typedrop.git