TypeDrop

2026-06-04 Challenge

2026-06-04 Medium

Typed Product Inventory Aggregator

You're building the reporting layer for an e-commerce warehouse system. Raw inventory records arrive as `unknown` JSON from a legacy ERP API; your engine must validate them, apply category-level discount rules via a typed strategy registry, and produce a strongly-typed per-category stock summary — with zero `any`.

Goals

  • Implement `validateProduct` to narrow `unknown` → `Product`, collecting ALL field errors before returning.
  • Declare `DiscountRegistry` as a mapped type over `Category` (every key required) and `InventoryReport` as an optional mapped type, then build `defaultDiscountRegistry` using the `satisfies` operator.
  • Implement `aggregateInventory` to validate each raw record, apply the registry's discount strategy per category, and accumulate per-category `CategorySummary` values into an `InventoryReport`.
  • Surface invalid records in `AggregatorResult.errors` keyed by their raw `id` string, falling back to `"__unknown__"` when `id` is absent or invalid.
challenge.ts

// Core types and main function signature

type Category = "electronics" | "apparel" | "grocery" | "furniture";

interface Product {
  id: string;
  name: string;
  category: Category;
  priceUsd: number;       // must be > 0
  stockUnits: number;     // must be >= 0, integer
  warehouseCode: string;
}

type Result<T, E> =
  | { ok: true;  value: T }
  | { ok: false; error: E };

type DiscountRegistry = { [K in Category]: (product: Product) => number };

type InventoryReport = { [K in Category]?: CategorySummary };

interface AggregatorResult {
  report: InventoryReport;
  validCount: number;
  invalidCount: number;
  errors: Record<string, ValidationError[]>;
}

// Main entry point
function aggregateInventory(
  rawRecords: unknown[],
  registry: DiscountRegistry = defaultDiscountRegistry
): AggregatorResult { /* TODO */ }
Hints (click to reveal)

Hints

  • For `validateProduct`, build an `errors: ValidationError[]` array, push every failing field into it, then return the Result at the end — never return early on the first failure.
  • Use `{ [K in Category]: DiscountStrategy }` for the registry (all keys required) and `{ [K in Category]?: CategorySummary }` for the report (all keys optional) — two small mapped types that enforce very different contracts.
  • When building `defaultDiscountRegistry`, write the object literal and append `satisfies DiscountRegistry` — TypeScript will catch any missing or misspelled category keys at compile time without widening the type.

Or clone locally

git clone -b challenge/2026-06-04 https://github.com/niltonheck/typedrop.git