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.

Or clone locally

git clone -b challenge/2026-08-30 https://github.com/niltonheck/typedrop.git