Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TypeScript

Discriminated unions, generic interfaces, private fields, async generators, type narrowing, and satisfies.

mdBook tree-sitter
type Loading = { state: "loading"; id: string };
type Ready<T> = { state: "ready"; value: T };
type Failed = { state: "failed"; error: Error };
type Result<T> = Loading | Ready<T> | Failed;

interface Cache<T extends { id: string }> {
  get(id: string): Promise<T | undefined>;
  put(value: T): Promise<void>;
}

class MemoryCache<T extends { id: string }>
  implements Cache<T> {
  readonly #items = new Map<string, T>();

  async get(id: string): Promise<T | undefined> {
    return this.#items.get(id);
  }

  async put(value: T): Promise<void> {
    this.#items.set(value.id, value);
  }
}

async function* pages<T>(
  url: URL,
  decode: (raw: unknown) => T[],
): AsyncGenerator<T> {
  let next: URL | undefined = url;

  while (next) {
    const response = await fetch(next);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    const body: unknown = await response.json();
    for (const item of decode(body)) {
      yield item;
    }

    const link = response.headers.get("x-next");
    next = link ? new URL(link, next) : undefined;
  }
}

function describe<T>(result: Result<T>): string {
  switch (result.state) {
    case "loading":
      return `loading ${result.id}`;
    case "ready":
      return JSON.stringify(result.value);
    case "failed":
      return result.error.message;
  }
}

const config = {
  retries: 3,
  mode: "eager",
} as const satisfies Record<string, unknown>;
type Loading = { state: "loading"; id: string };
type Ready<T> = { state: "ready"; value: T };
type Failed = { state: "failed"; error: Error };
type Result<T> = Loading | Ready<T> | Failed;

interface Cache<T extends { id: string }> {
  get(id: string): Promise<T | undefined>;
  put(value: T): Promise<void>;
}

class MemoryCache<T extends { id: string }>
  implements Cache<T> {
  readonly #items = new Map<string, T>();

  async get(id: string): Promise<T | undefined> {
    return this.#items.get(id);
  }

  async put(value: T): Promise<void> {
    this.#items.set(value.id, value);
  }
}

async function* pages<T>(
  url: URL,
  decode: (raw: unknown) => T[],
): AsyncGenerator<T> {
  let next: URL | undefined = url;

  while (next) {
    const response = await fetch(next);
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    const body: unknown = await response.json();
    for (const item of decode(body)) {
      yield item;
    }

    const link = response.headers.get("x-next");
    next = link ? new URL(link, next) : undefined;
  }
}

function describe<T>(result: Result<T>): string {
  switch (result.state) {
    case "loading":
      return `loading ${result.id}`;
    case "ready":
      return JSON.stringify(result.value);
    case "failed":
      return result.error.message;
  }
}

const config = {
  retries: 3,
  mode: "eager",
} as const satisfies Record<string, unknown>;