TypeDrop

2026-09-25 Challenge

2026-09-25 Medium

Typed CSV Pipeline with Schema Validation & Aggregation

You're building the data-ingestion core for a financial reporting tool. Raw CSV text arrives from uploaded files, each column is validated against a declared schema, and the parsed rows are aggregated into a strongly-typed summary report — all without a single `any` or unsafe cast.

Goals

  • Complete the `SchemaRow<S>` mapped type so it produces a correct intersection of per-column object types using `UnionToIntersection`.
  • Implement `parseCsv` to split raw CSV text, validate headers, parse each cell to its declared kind, handle optional nulls, and collect errors without `any`.
  • Implement `aggregateRows` to group rows by a string-valued key and sum specified numeric columns, returning a fully-typed `GroupSums` object.
  • Implement `runPipeline` to compose parsing and aggregation into a single typed call, returning an empty report when no rows are valid.
challenge.ts

// Key types at a glance:
type ColumnKind = "string" | "number" | "boolean" | "date";

type KindToPrimitive<K extends ColumnKind> =
  K extends "string"  ? string  :
  K extends "number"  ? number  :
  K extends "boolean" ? boolean :
  K extends "date"    ? Date    : never;

interface ColumnDef<K extends ColumnKind = ColumnKind> {
  readonly name: string;
  readonly kind: K;
  readonly optional?: boolean;
}

// SchemaRow maps a tuple of ColumnDefs → one intersected object type
type SchemaRow<S extends readonly ColumnDef[]> = UnionToIntersection<...>;

// Main entry point:
function parseCsv<S extends readonly ColumnDef[]>(
  rawCsv: string,
  schema: S
): CsvParseOutput<S>;

function aggregateRows<
  S extends readonly ColumnDef[],
  Spec extends AggregationSpec<S>
>(rows: SchemaRow<S>[], spec: Spec): GroupSums<S, Spec>;
Hints (click to reveal)

Hints

  • For `SchemaRow<S>`, first map the tuple to a union of single-key objects, then wrap that union in `UnionToIntersection<...>` to collapse it into one flat object.
  • In `parseCsv`, a `switch` on `col.kind` is the cleanest way to narrow to the right branch — the compiler will know the return type of each arm without any casting.
  • For `NumericKeys<S>` and the `groupBy` constraint in `AggregationSpec`, use a mapped type that maps each key to itself or `never`, then index with `[keyof SchemaRow<S>]` to filter.

Or clone locally

git clone -b challenge/2026-09-25 https://github.com/niltonheck/typedrop.git