TypeDrop

2026-03-18 Challenge

2026-03-18 Medium

Typed API Pagination Cursor Engine

You're building the data-fetching layer for an analytics dashboard that loads large datasets from a paginated REST API. Each resource type has its own shape, and the engine must handle cursor-based pagination, typed per-resource response validation, and aggregation into a single fully-typed result — surfaced through a `Result<T, E>` type with zero `any`.

Goals

  • Implement `buildValidatorRegistry` with runtime validators that narrow `unknown` to each resource type using only type-safe field checks.
  • Implement `parseRawPage` to validate the raw API response shape and each record using the registry, returning the correct discriminated `PaginationError` variant on failure.
  • Implement `fetchAllPages` to drive cursor-based pagination, accumulate records across pages, and handle both fetch errors and validation errors through the `Result<T, E>` type.
  • Use the mapped type `ValidatorRegistry` and indexed access `ResourceMap[K]` so that every function is fully generic over `ResourceKind` with no `any` or type assertions.
challenge.ts
// Core types you'll work with:

type ResourceMap = {
  users:    UserRecord;
  orders:   OrderRecord;
  products: ProductRecord;
};

type ResourceKind = keyof ResourceMap;   // "users" | "orders" | "products"

type ValidatorRegistry = {
  [K in ResourceKind]: Validator<ResourceMap[K]>;
};

// Main functions to implement:

function parseRawPage<K extends ResourceKind>(
  kind: K,
  raw: unknown,
  registry: ValidatorRegistry
): Result<PageResult<K>, PaginationError>;

async function fetchAllPages<K extends ResourceKind>(
  kind: K,
  fetcher: PageFetcher,
  registry: ValidatorRegistry,
  maxPages?: number
): Promise<Result<AggregatedResult<K>, PaginationError>>;

function buildValidatorRegistry(): ValidatorRegistry;
Hints (click to reveal)

Hints

  • A mapped type `{ [K in ResourceKind]: Validator<ResourceMap[K]> }` lets you index the registry with a generic `K` and get back the right validator type automatically — lean on that to avoid casting.
  • To narrow `unknown` to an object with known keys, check `typeof raw === 'object' && raw !== null` first, then use `'fieldName' in raw` before accessing the property — TypeScript will accept this without assertions.
  • In `fetchAllPages`, keep a `cursor: string | null = null` variable, loop with a `while` condition, and break out when `nextCursor` is null or you've hit `maxPages` — wrap the entire fetcher call in a `try/catch`.

Or clone locally

git clone -b challenge/2026-03-18 https://github.com/niltonheck/typedrop.git