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.

Or clone locally

git clone -b challenge/2026-10-07 https://github.com/niltonheck/typedrop.git