TypeDrop

2026-05-29 Challenge

2026-05-29 Easy

Typed Shopping Cart Aggregator

You're building the order-summary layer for an e-commerce checkout flow. Raw cart items arrive as `unknown` from a localStorage deserializer; your engine must validate them, apply typed discount rules, and produce a strongly-typed order summary — with zero `any`.

Goals

  • Implement `validateCartItem` to narrow `unknown` → `ValidatedCartItem` using a branded type, throwing a `TypeError` on any invalid field.
  • Implement `buildLineItems` to map validated items into `LineItem` objects with computed line totals.
  • Implement `applyDiscounts` to accumulate discounts from all three `DiscountRule` kinds (flat, percentage, bogo) against the original subtotal, clamped to [0, subtotal].
  • Compose everything in `buildOrderSummary` to produce a fully-typed `OrderSummary` with a non-negative total.
challenge.ts
// Key types at a glance

type Category = "electronics" | "clothing" | "food" | "books";

type ValidatedCartItem = RawCartItem & { readonly __validated: true };

type DiscountRule =
  | { kind: "flat"; amountInCents: number }
  | { kind: "percentage"; percent: number }
  | { kind: "bogo"; category: Category };

interface OrderSummary {
  lineItems: LineItem[];
  subtotalInCents: number;
  discountInCents: number;
  totalInCents: number;   // never negative
  itemCount: number;
}

// Functions you must implement:
declare function validateCartItem(raw: unknown): ValidatedCartItem;
declare function buildLineItems(items: ReadonlyArray<ValidatedCartItem>): LineItem[];
declare function applyDiscounts(
  subtotalInCents: number,
  items: ReadonlyArray<ValidatedCartItem>,
  rules: ReadonlyArray<DiscountRule>
): number;
declare function buildOrderSummary(
  items: ReadonlyArray<ValidatedCartItem>,
  rules: ReadonlyArray<DiscountRule>
): OrderSummary;
Hints (click to reveal)

Hints

  • Use a type predicate (`value is ValidatedCartItem`) or an explicit brand assignment inside `validateCartItem` — remember, no `as` allowed means you must narrow field-by-field before returning.
  • For the discriminated union in `applyDiscounts`, a `switch` on `rule.kind` gives you exhaustive narrowing and TypeScript will enforce that every branch is handled.
  • The `bogo` discount only needs the validated items array — filter by `rule.category`, then sum each matching item's `lineTotalInCents` and halve it with `Math.floor`.

Or clone locally

git clone -b challenge/2026-05-29 https://github.com/niltonheck/typedrop.git