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