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