TypeDrop
2026-08-06 Challenge
2026-08-06
Medium
Typed Paginated API Client with Cursor-Based Iteration
You're building the data-fetching layer for an admin dashboard that consumes a paginated REST API. Responses arrive in cursor-based pages, and the client must lazily iterate through all pages, accumulate typed results, and surface per-page errors as a typed `Result<T, FetchError>` — never throwing, and never losing the shape of the resource being fetched.
Goals
- Define a discriminated Result<T,E> union and a FetchError variant union with three error kinds.
- Implement the fetchPages<T> async generator that walks a cursor chain, respects maxPages and AbortSignal, and yields a typed PageOutcome<T> for every fetch attempt.
- Implement collectAll<T> to drain the generator and accumulate items and errors into a typed summary without ever throwing.
- Define the ExtractOk<R> conditional type that infers and returns the Ok value type from any Result<T,E>.
challenge.ts
// Core types you must define and implement:
interface Page<T> {
items: T[];
nextCursor: string | null;
total: number;
}
type Result<T, E> =
| { tag: "ok"; value: T }
| { tag: "err"; error: E };
type FetchError =
| { kind: "network"; message: string }
| { kind: "http"; status: number; body: string }
| { kind: "parse"; raw: string };
type PageFetcher<T> = (cursor: string | null) => Promise<Result<Page<T>, FetchError>>;
interface PageOutcome<T> {
result: Result<Page<T>, FetchError>;
pageIndex: number;
cursorUsed: string | null;
}
// Main function you must implement:
async function* fetchPages<T>(
config: PaginatedClientConfig<T>
): AsyncGenerator<PageOutcome<T>, void, unknown> { /* ... */ }
Hints (click to reveal)
Hints
- For ExtractOk, use `R extends Result<infer T, infer _E> ? T : never` — the `infer` keyword lets you pull T out of the Result structure.
- An async generator function uses `yield` to emit values and `return` to finish; give it the explicit return type `AsyncGenerator<PageOutcome<T>, void, unknown>` so TypeScript validates every yield site.
- To narrow a Result inside collectAll, check `outcome.result.tag === 'ok'` — TypeScript will then know `.value` exists and has type `Page<T>`.
Useful resources
Or clone locally
git clone -b challenge/2026-08-06 https://github.com/niltonheck/typedrop.git