TypeDrop
2026-10-07 Challenge
2026-10-07
Medium
Typed Paginated API Client with Result Chaining
You're building the data-fetching layer for an analytics dashboard that consumes a paginated REST API. The client must handle multi-page traversal, surface typed errors without throwing, and aggregate all pages into a single typed collection — all enforced at compile time with no unsafe casts.
Goals
- Define the `Result<T, E>` discriminated union and its `ok` / `err` convenience constructors with correct generic types.
- Implement `mapResult` and `flatMapResult` using type narrowing on the `success` discriminant — no type assertions allowed.
- Implement `fetchPage` to wrap `simulateFetch`, catching network rejections and adapter parse failures into typed `ApiError` results.
- Implement `fetchAllPages` to cursor-walk all pages sequentially, short-circuiting on the first `Err` and collecting all items on success.
challenge.ts
// Key types and main function signatures
export type Ok<T> = { success: true; value: T };
export type Err<E> = { success: false; error: E };
export type Result<T, E> = Ok<T> | Err<E>;
export interface Page<T> {
items: T[];
nextCursor: string | null;
totalCount: number;
}
export type PageAdapter<T> = (raw: unknown) => Page<T>;
// Traverse all pages, collecting items; short-circuit on first error
export async function fetchAllPages<T>(
endpoint: string,
adapter: PageAdapter<T>,
): Promise<Result<T[], ApiError>> { /* TODO */ }
// Functor map over the ok branch
export function mapResult<T, U, E>(
result: Result<T, E>,
fn: (value: T) => U,
): Result<U, E> { /* TODO */ }
// Monadic bind — chain a Result-returning function
export function flatMapResult<T, U, E>(
result: Result<T, E>,
fn: (value: T) => Result<U, E>,
): Result<U, E> { /* TODO */ }
Hints (click to reveal)
Hints
- Narrow `Result<T, E>` by checking `result.success === true` — TypeScript will then know the variant and give you `.value` or `.error` without casting.
- For `fetchPage`, use two nested try/catch blocks (or one with careful branching): one for the network call and one for the adapter — each maps to a different `ApiErrorCode`.
- In `fetchAllPages`, initialize `cursor` as `string | null = null` and loop while the last page's `nextCursor` is not null — the loop body must be sequential (`await` inside the loop), not parallel.
Useful resources
Or clone locally
git clone -b challenge/2026-10-07 https://github.com/niltonheck/typedrop.git