TypeDrop
A new TypeScript challenge every day. Sharpen your types.
TypeDrop delivers a fresh TypeScript challenge every day, generated by AI. Pick a challenge, open it in StackBlitz (preferred) or CodeSandbox (or clone it locally), and make the tests pass. No accounts, no setup — just you and the type system.
2026-06-19
Medium
Typed CSV Report Pipeline with Aggregation & Validation
You're building the data-export layer for an analytics dashboard. Raw CSV rows arrive as `unknown[]` from a file-upload parser; your pipeline must validate each row into a strongly-typed record, aggregate metrics in a single pass, and return a typed report summary — with zero `any`.
Goals
- Implement `validateRow` to fully narrow `unknown` → `SalesRecord` using field-by-field type guards, returning the first failing field in a typed `ValidationFailure`.
- Implement `aggregateRecords` to compute `grandTotalRevenue` and a per-region `Record<Region, RegionSummary>` in a single loop pass, with all monetary values rounded to 2 decimal places.
- Implement `runPipeline` to orchestrate validation and aggregation, collecting errors and returning a fully-typed `PipelineResult` discriminated union — never throwing.
- Ensure all four `Region` values always appear in `byRegion` (even if a region has zero records), using `Record<Region, RegionSummary>`.
challenge.ts
// Key types & main function signatures at a glance
export type Region = "north" | "south" | "east" | "west";
export interface SalesRecord {
id: string;
region: Region;
product: string;
quantity: number; // positive integer
unitPrice: number; // positive number
saleDate: string; // "YYYY-MM-DD"
}
export type ValidationResult<T> =
| { readonly status: "ok"; readonly value: T }
| { readonly status: "error"; readonly rowIndex: number;
readonly field: keyof SalesRecord | "unknown"; readonly message: string };
export type PipelineResult =
| { status: "complete"; report: ReportSummary }
| { status: "empty"; message: string }
| { status: "fatal"; error: string };
export function validateRow(row: unknown, rowIndex: number): ValidationResult<SalesRecord>;
export function aggregateRecords(records: SalesRecord[]): Pick<ReportSummary, "grandTotalRevenue" | "byRegion">;
export function runPipeline(rawRows: unknown[]): PipelineResult;
Hints (click to reveal)
Hints
- To narrow `unknown` to an object with known keys, first check `typeof row === 'object' && row !== null`, then use `'fieldName' in row` before accessing each property — this keeps TypeScript happy with no `as` or `any`.
- For the `Region` guard, define a `const REGIONS = ['north','south','east','west'] as const` array and use `REGIONS.includes(value as Region)` — or write an explicit type-predicate function `isRegion(v: unknown): v is Region`.
- Seed `byRegion` before your aggregation loop by mapping over all four region literals so every key is always present; `Math.round(x * 100) / 100` is a clean way to round to 2 decimal places.
Useful resources
Or clone locally
git clone -b challenge/2026-06-19 https://github.com/niltonheck/typedrop.git