TypeDrop

2026-08-19 Challenge

2026-08-19 Hard

Typed Schema-Validated API Response Normalizer with Result Monad & Conditional Types

You're building the ingestion layer for a multi-source data warehouse. Raw API responses arrive as `unknown` JSON blobs from heterogeneous vendors — each with its own shape, required fields, and numeric/string quirks. Your normalizer must validate, coerce, and reshape each blob into a strongly-typed domain record, surfacing structured, exhaustively-matchable validation errors — all without a single `any` or unsafe cast.

Goals

  • Implement the Result<T,E> monad helpers (ok, err, isOk, isErr) and the three branded primitive validators (nonEmptyString, positiveNumber, isoDateString).
  • Implement normalize<R>() so it collects ALL field errors in a single pass rather than short-circuiting on the first failure, returning Ok<R> or Err<ValidationError>.
  • Complete the Schema<R> mapped type, the SchemaRegistry<M> generic registry, and normalizeFromRegistry so the return type is inferred as Result<M[K], ValidationError> without any type assertions.
  • Define the NormalizedOutput<S> conditional type using infer to extract T from FieldSchema<T>, and wire up orderSchema and userSchema with the satisfies constraint.
challenge.ts
// Key types & main function signatures

export type Brand<T, B extends string> = T & { readonly __brand: B };
export type ISODateString  = Brand<string, "ISODateString">;
export type PositiveNumber = Brand<number, "PositiveNumber">;
export type NonEmptyString = Brand<string, "NonEmptyString">;

export type Result<T, E> = { readonly tag: "ok"; readonly value: T }
                         | { readonly tag: "err"; readonly error: E };

export type FieldSchema<T> = {
  readonly key: string;
  readonly validate: (raw: unknown) => Result<T, FieldError>;
};

export type Schema<R extends Record<string, unknown>> = {
  [K in keyof R]: FieldSchema<R[K]>;   // ← mapped type to complete
};

// Normalize a single unknown blob against a schema
export function normalize<R extends Record<string, unknown>>(
  schema: Schema<R>,
  raw: unknown,
  source: string
): Result<R, ValidationError> { /* TODO */ }

// Dispatch to the correct schema by registry key
export function normalizeFromRegistry<
  M extends Record<string, Record<string, unknown>>,
  K extends keyof M & string
>(registry: SchemaRegistry<M>, source: K, raw: unknown): Result<M[K], ValidationError> { /* TODO */ }

// Conditional type: extract T from FieldSchema<T>
export type NormalizedOutput<S> = /* TODO: conditional + infer */ never;
Hints (click to reveal)

Hints

  • For branded types, you can 'stamp' a value at runtime with `value as Brand<typeof value, 'X'>` — but only inside the validator that has already confirmed the value is safe.
  • In normalize(), iterate over Object.keys(schema) and accumulate FieldError[] before deciding whether to return ok or err — the tricky part is assembling the result record with the right type.
  • For NormalizedOutput<S>, think: `S extends FieldSchema<infer T> ? T : never` — the conditional type pattern mirrors how zod's z.infer works.

Or clone locally

git clone -b challenge/2026-08-19 https://github.com/niltonheck/typedrop.git