TypeDrop

2026-05-03 Challenge

2026-05-03 Medium

Typed In-Memory Search Index

You're building the client-side search layer for a documentation site. Raw document payloads arrive as `unknown` from a local JSON bundle; your engine must validate them, build an inverted index over configurable fields, and return strongly-typed ranked results — with zero `any`.

Goals

  • Implement `ExtractFields<C>` and `WeightMap<F>` using `infer` and built-in utility types.
  • Implement `validateDocument` to narrow `unknown` → `IndexedDocument` and return a discriminated-union `ValidationResult` — no `any` or `as`.
  • Implement `tokenise` to lower-case, split, and filter a string into clean tokens.
  • Implement the full `SearchIndex<F>` class: build a weighted inverted index on `add`, and return ranked `SearchResult` discriminated-union hits on `search`.
challenge.ts
// Key types & main class signature

interface IndexConfig<F extends string> {
  fields: readonly F[];
  fieldWeights?: Partial<Record<F, number>>;
}

interface SearchHit {
  id: string;
  score: number;
  fields: Record<string, string>;
}

type SearchResult =
  | { kind: "results"; hits: SearchHit[]; query: string }
  | { kind: "empty"; query: string };

type ValidationResult =
  | { kind: "ok"; document: IndexedDocument }
  | { kind: "error"; reason: string; raw: unknown };

// ── Your main entry point ────────────────────────────────────
class SearchIndex<F extends string> {
  constructor(config: IndexConfig<F>) { /* TODO */ }
  add(raw: unknown): ValidationResult  { /* TODO */ }
  search(query: string): SearchResult  { /* TODO */ }
  size(): number                       { /* TODO */ }
}
Hints (click to reveal)

Hints

  • For `validateDocument`, use `typeof` and `in` operator checks to narrow `unknown` step by step — TypeScript will track the type for you without needing `as`.
  • In the `SearchIndex` constructor, build the `WeightMap<F>` by spreading a default-weight object over `config.fieldWeights` — `Object.fromEntries` paired with `config.fields.map` keeps it type-safe.
  • The inverted index maps `token → Map<docId, weightedCount>` — when indexing, multiply the raw term-frequency by the field's weight before storing.

Or clone locally

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