TypeDrop
2026-08-13 Challenge
2026-08-13
Medium
Typed Paginated API Client with Cursor-Based Iteration
You're building the data-access layer for an analytics dashboard that streams records from a paginated REST API. The API uses cursor-based pagination, returns heterogeneous resource types, and can fail mid-stream — your client must expose a strongly-typed async generator that yields individual records, handles errors as typed Results, and supports early cancellation via AbortSignal.
Goals
- Define a discriminated-union Result<T,E> type with Ok/Err variants and a three-way FetchError union identified by a `kind` literal.
- Implement Page<T> and PaginatedFetchOptions<T,U> with a default generic parameter and an optional per-item transform.
- Build the paginatedFetch async generator that handles AbortSignal cancellation, network errors, and cursor advancement — yielding typed Results.
- Implement collectResults to drain the generator into separate values/errors arrays, plus isOk/isErr type-guard functions that correctly narrow the union.
challenge.ts
interface User { id: number; name: string }
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E> = Ok<T> | Err<E>;
type FetchError =
| { kind: "network"; message: string; retryable: boolean }
| { kind: "parse"; message: string; raw: string }
| { kind: "abort"; message: string };
type Page<T> = {
items: T[];
nextCursor: string | null;
totalCount?: number;
};
type PaginatedFetchOptions<T, U = T> = {
fetcher: (cursor: string | null, signal: AbortSignal) => Promise<Page<T>>;
signal: AbortSignal;
transform?: (item: T) => U;
startCursor?: string | null;
};
export async function* paginatedFetch<T, U = T>(
options: PaginatedFetchOptions<T, U>
): AsyncGenerator<Result<U, FetchError>> { /* TODO */ }
Hints (click to reveal)
Hints
- For the default generic parameter trick in PaginatedFetchOptions, try `transform?: (item: T) => U` — when omitted, the caller gets U = T automatically.
- An async generator is declared with `async function*`; use `yield` inside a `for...of` loop over `page.items`, and check `signal.aborted` before every fetcher call.
- To distinguish an AbortError from a generic network error, check `err instanceof DOMException && err.name === 'AbortError'` inside your catch block.
Useful resources
Or clone locally
git clone -b challenge/2026-08-13 https://github.com/niltonheck/typedrop.git