0Pricing
TypeScript Academy · Lesson

Strongly-typed builders

Design chainable builders that accumulate typed options; ensure required steps and valid combinations using generics and conditional types.

Intro

Goal: Build chainable APIs that encode progress in the type. Each step returns a new typed state so invalid orders fail at compile time.

  • Accumulate options in a generic
  • Gate steps with flags
  • Keep DX simple

State flags

Each step returns a new type of state; build is accessible only when the required flags are true.

type Flags = { method: boolean; url: boolean }

type With<F extends Partial<Flags>> = { flags: F }

type True = true

type Builder<F extends Partial<Flags>, O extends Record<string, unknown>> = With<F> & {
  method<M extends "GET" | "POST">(m: M): Builder<F & { method: True }, O & { method: M }>
  url<U extends `/${string}`>(u: U): Builder<F & { url: True }, O & { url: U }>
  build(this: Builder<{ method: True; url: True }, any>): O
}

function createBuilder(): Builder<{}, {}> {
  return {
    flags: {},
    method(m) { return { ...this, flags: { ...this.flags, method: true }, method: this.method, url: this.url, build: this.build } as any },
    url(u) { return { ...this, flags: { ...this.flags, url: true }, method: this.method, url: this.url, build: this.build } as any },
    build() { return this as any }
  } as any
}

const b = createBuilder()
const good = b.method("GET").url("/users").build()   // ok
// const bad = b.build() // error: build requires method & url flags

All lessons in this course

  1. Tuple rest elements, curry types
  2. Compose functions and arguments safely
  3. Strongly-typed builders
← Back to TypeScript Academy