TypeDrop

2026-06-05 Challenge

2026-06-05 Easy

Typed Recipe Ingredient Scaler

You're building the recipe engine for a meal-planning app. Raw recipe data arrives as `unknown` from a third-party nutrition API; your engine must validate it, scale each ingredient's quantity to a requested serving size, and return a strongly-typed scaled recipe — with zero `any`.

Goals

  • Implement `isUnit()` as a strict type guard using the `UNITS` constant.
  • Implement `parseIngredient()` and `parseRecipe()` to validate `unknown` input into typed domain objects, returning discriminated `ValidationError` values.
  • Implement `scaleRecipe()` to proportionally adjust ingredient quantities and return a `ScaledRecipe` with `originalServings` and `targetServings` fields.
  • Implement `parseAndScale()` to compose the validation and scaling steps, tagging pipeline errors with a `stage` discriminant.
challenge.ts

// Key types & main function signature

export type Unit = "g" | "kg" | "ml" | "l" | "tsp" | "tbsp" | "cup" | "piece" | "pinch";

export interface Ingredient { name: string; quantity: number; unit: Unit; }
export interface Recipe     { id: string; title: string; servings: number; ingredients: Ingredient[]; }
export interface ScaledRecipe extends Recipe { originalServings: number; targetServings: number; }

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

export type PipelineError =
  | { stage: "validation"; error: ValidationError }
  | { stage: "scaling";    error: ScalingError    };

// Full pipeline: validate raw unknown → scale to targetServings
export function parseAndScale(
  raw: unknown,
  targetServings: number
): Result<ScaledRecipe, PipelineError> { /* TODO */ }
Hints (click to reveal)

Hints

  • Use `Array.prototype.includes` with a cast-free approach on `UNITS` to implement `isUnit` — remember `UNITS` is `readonly Unit[]`, so `(UNITS as readonly unknown[]).includes(value)` keeps you type-safe.
  • In `parseRecipe`, check `typeof raw === 'object' && raw !== null` before accessing any fields, then use `'fieldName' in raw` to confirm keys exist before reading them.
  • For `parseAndScale`, unwrap each `Result` step-by-step and wrap errors with the correct `stage` tag before returning — no casting needed.

Or clone locally

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