TypeDrop

2026-02-21 Challenge

2026-02-21 Hard

Paginated API Client with Typed Result Accumulation

You're building a typed API client for an analytics platform that exposes cursor-based paginated endpoints. The client must traverse all pages concurrently (up to a configurable limit), accumulate results into a strongly-typed aggregate, and surface per-page errors without aborting the entire fetch — all without a single `any` or type assertion.

Goals

  • Define branded string types `Cursor` and `EndpointPath` using a `unique symbol` brand and a single generic branding helper — no `as` outside that helper.
  • Model `FetchResult<T>` and `AccumulatedResult<T>` as discriminated unions so TypeScript can narrow them without type assertions.
  • Implement `withConcurrencyLimit<R>` that runs async task factories with a configurable concurrency cap and returns results in original order.
  • Implement `fetchAllPages<T>` that traverses cursor-based pages using `withConcurrencyLimit`, collects items in page order, and accumulates per-page errors without aborting the traversal.
challenge.ts
// Key types and main function signature

declare const __brand: unique symbol;
type Brand<B> = { readonly [__brand]: B };

type Cursor      = string & Brand<"Cursor">;
type EndpointPath = string & Brand<"EndpointPath">;

type PageResponse<T> = {
  items:      T[];
  nextCursor: Cursor | undefined;
  pageIndex:  number;
};

type FetchResult<T> =
  | { ok: true;  data: T }
  | { ok: false; error: PageError };

type AccumulatedResult<T> = /* discriminated by `ok` — you define it */;

type PageFetcher<T> =
  (req: PageRequest) => Promise<FetchResult<PageResponse<T>>>;

// ── Core entry point ──────────────────────────────────────────
async function fetchAllPages<T>(
  endpoint: EndpointPath,
  fetcher:  PageFetcher<T>,
  options?: FetchAllOptions
): Promise<AccumulatedResult<T>>
Hints (click to reveal)

Hints

  • For `withConcurrencyLimit`, track results by index in a pre-allocated array and maintain a pointer to the next task to start — avoid `Promise.all` on the full set.
  • The `AccumulatedResult<T>` discriminated union should mirror `FetchResult` — an `ok: true` branch with no `errors` field forces callers through `handleAccumulatedResult` to access errors safely.
  • When traversing pages, only enqueue the next cursor if the current page succeeded — failed pages cannot yield a `nextCursor`, so that branch of the graph is naturally pruned.

Or clone locally

git clone -b challenge/2026-02-21 https://github.com/niltonheck/typedrop.git