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.
Useful resources
Or clone locally
git clone -b challenge/2026-02-21 https://github.com/niltonheck/typedrop.git