TypeDrop
2026-08-30 Challenge
2026-08-30
Medium
Typed Paginated API Client with Result Chaining & Cursor Inference
You're building the data-access layer for an internal admin dashboard that fetches paginated resources from a REST API. Each endpoint returns a different resource shape, and the client must handle cursor-based pagination, surface typed per-page results, accumulate all pages into a final collection, and propagate fetch errors without losing their structure.
Goals
- Implement `ok` and `err` constructors that preserve narrow literal types without widening.
- Implement `fetchAllPages` to sequentially follow cursors, short-circuit on the first `Err`, and accumulate all items into a typed `FetchAllResult<T>`.
- Implement `mapFetchResult` to transform items inside an `Ok` result while passing `Err` values through unchanged, fully generic over T, U, and E.
- Implement `matchResult` so TypeScript infers the return type R from the two callback signatures without the caller providing it explicitly.
challenge.ts
// Core types you'll work with:
type Ok<T> = { readonly status: "ok"; readonly value: T };
type Err<E> = { readonly status: "err"; readonly error: E };
type Result<T, E> = Ok<T> | Err<E>;
type Page<T> = {
readonly items: T[];
readonly nextCursor: string | null;
readonly totalCount: number;
};
type FetchAllResult<T> = {
readonly items: T[];
readonly pagesFetched: number;
readonly reportedTotal: number;
};
type PageFetcher<T, E> = (
opts: { readonly cursor: string | undefined; readonly pageSize: number }
) => Promise<Result<Page<T>, E>>;
// Main function you must implement:
async function fetchAllPages<T, E>(
fetcher: PageFetcher<T, E>,
pageSize: number
): Promise<Result<FetchAllResult<T>, E>> { /* TODO */ }
Hints (click to reveal)
Hints
- For `fetchAllPages`, a `while (true)` loop that checks `nextCursor` each iteration is cleaner than recursion — break when `nextCursor` is `null`.
- The discriminant `.status === 'ok'` is all TypeScript needs to narrow `Result<T,E>` into `Ok<T>` — no type assertions required.
- To keep `matchResult`'s return type R inferred, simply declare it as a third type parameter after T and E and let the compiler unify it from both callbacks.
Useful resources
Or clone locally
git clone -b challenge/2026-08-30 https://github.com/niltonheck/typedrop.git