TypeDrop
2026-03-17 Challenge
2026-03-17
Easy
Typed Book Club Reading List Builder
You're building the reading list feature for a book club app. Members submit raw book entries from a form, and you must validate them, tag each book with a derived reading status, and produce a sorted, fully typed reading list — with zero `any`.
Goals
- Implement `ok` and `err` generic helpers that construct a discriminated-union `Result<T, E>`.
- Write `isGenre` as a type-predicate function that narrows `unknown` to the `Genre` union.
- Implement `parseBook` to validate every field of an `unknown` input against the `Book` interface, deriving `status` via `deriveStatus` and returning the first `BookValidationError` on failure.
- Implement `buildReadingList` to parse all raw entries, sort valid books case-insensitively by title, and produce a `summary` `Record` with a count for every `ReadingStatus` key — even zeros.
challenge.ts
export type Genre = "fiction" | "non-fiction" | "biography" | "science" | "history";
export type ReadingStatus = "not-started" | "in-progress" | "completed";
export interface Book {
id: string;
title: string;
author: string;
genre: Genre;
totalPages: number;
pagesRead: number;
status: ReadingStatus; // derived — NOT accepted from raw input
}
export type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
export interface ReadingList {
books: Book[]; // sorted A-Z by title
summary: Record<ReadingStatus, number>; // count per status (all keys present)
rejected: Array<{ input: unknown; error: BookValidationError }>;
}
// Main entry point
export function buildReadingList(rawEntries: unknown[]): ReadingList { ... }
Hints (click to reveal)
Hints
- A type-predicate (`value is Genre`) lets TypeScript narrow the type in the caller's scope — use an array of the valid strings and `.includes()` after a `typeof` check.
- When building the `summary` Record, initialise all three `ReadingStatus` keys to `0` before iterating so no key is ever missing — `Record<ReadingStatus, number>` requires all keys.
- In `parseBook`, check `typeof input === 'object' && input !== null` first, then use `'id' in input` before indexing — this keeps you `any`-free under `strict: true`.
Useful resources
Or clone locally
git clone -b challenge/2026-03-17 https://github.com/niltonheck/typedrop.git