typed-fetch

TypeScript Types

Complete TypeScript API reference for typed-fetch

TypeScript Types

Complete reference for all TypeScript types exported by typed-fetch.

Main Export: typedFetch

export async function typedFetch<K extends TypedEndpointKey>(
  input: RequestInfo | URL,
  init?: RequestInit,
  options: TypedFetchOptions<K>,
): Promise<TypedFetchResult<K>>;

Alias: tFetch (shorter name for convenience)

Parameters

input

The URL to fetch. Can be a string, URL object, or Request:

await typedFetch("https://api.example.com/users/1", ...);
await typedFetch(new URL("/api/users/1", baseUrl), ...);

init (optional)

Standard fetch RequestInit options:

await typedFetch(
  url,
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name: "John" }),
  },
  { endpointKey },
);

options

typed-fetch specific options:

interface TypedFetchOptions<K extends TypedEndpointKey> {
  /** Required. Endpoint pattern, e.g. "GET /users/:id". */
  endpointKey: K;
  /** Optional config overrides. */
  config?: Partial<TypedFetchConfig>;
  /** Path to a typed-fetch config file. */
  configPath?: string;
  /** Optional cache for deduplication, stale-while-revalidate, and retry. */
  cache?: TypedFetchCache;
}

TypedEndpointKey

The type accepted by endpointKey. IDEs suggest all known keys while still allowing any string:

export type TypedEndpointKey = KnownEndpointKey | (string & {});
//                              ^-- auto-completed  ^-- any string accepted

Known keys come from TypedFetchGeneratedResponses (auto-generated) and TypedFetchUserEndpoints (manual declarations).

Return Type: TypedFetchResult

type TypedFetchResult<K extends TypedEndpointKey> =
  | TypedFetchNetworkError<K>
  | (K extends KnownEndpointKey
      ? KnownEndpointResult<K>   // per-status typed via discriminated union
      : { status: number; ok: boolean; data: unknown; response: Response });

On success (any HTTP response including 4xx/5xx)

{
  endpoint: K;        // The endpointKey you provided
  status: number;     // HTTP status code (200, 404, 500, …)
  ok: boolean;        // true if status is 2xx (200–208, 226)
  data: unknown;      // Parsed JSON body (typed after generate)
  response: Response; // Original Response object
  error?: undefined;
}

On network error (status === 0)

export type TypedFetchNetworkError<K extends TypedEndpointKey> = {
  endpoint: K;
  status: 0;          // Always 0 — no HTTP response received
  ok: false;
  data: undefined;
  response: null;
  error: Error;       // Network error details
};

Discriminated Unions

The result type is designed for discriminated unions based on status:

const result = await typedFetch(url, init, { endpointKey: "GET /users/:id" });

if (result.status === 200) {
  // result.data is typed for the 200 shape
  console.log(result.data.name);
} else if (result.status === 404) {
  console.log(result.data.error);
} else if (result.status === 0) {
  // Network error
  console.error(result.error.message);
}

Generated Types: TypedFetchGeneratedResponses

After running npx typed-fetch generate, a .d.ts file augments this interface:

// src/generated/typed-fetch.d.ts  (auto-generated — do not edit)
declare module "@phumudzo/typed-fetch" {
  interface TypedFetchGeneratedResponses {
    "GET /users/:id": {
      200: { id: number; name: string; email: string };
      404: null;
    };
    "POST /users": {
      201: { id: number };
      400: { message: string };
    };
  }
}

Manual Types: TypedFetchUserEndpoints

Augment this interface to declare types for endpoints you haven't observed yet, or to override generated types. User-defined entries take priority over generated ones.

// src/typed-fetch.endpoints.d.ts
declare module "@phumudzo/typed-fetch" {
  interface TypedFetchUserEndpoints {
    "GET /users/:id": {
      200: { id: number; name: string; email: string; role: "admin" | "user" };
      404: { error: string };
    };
    // Third-party API without observation
    "GET /repos/:owner/:repo": {
      200: { id: number; name: string; stargazers_count: number };
      404: { message: string };
    };
  }
}
export {};

Caching: TypedFetchCache and CacheOptions

export type CacheOptions = {
  staleTime?: number;                           // default: 0
  gcTime?: number;                              // default: 300_000 (5 min)
  retry?: number | false;                       // default: 3
  retryDelay?: (attempt: number) => number;     // default: exponential
};

export class TypedFetchCache {
  buildKey(input: RequestInfo | URL, method: string): string;
  get<T>(key: string): { result: T; fetchedAt: number; endpointKey: string } | undefined;
  isStale(entry: { fetchedAt: number }): boolean;
  set<T>(key: string, result: T, endpointKey: string): void;
  getInFlight<T>(key: string): Promise<T> | undefined;
  setInFlight<T>(key: string, promise: Promise<T>): void;
  clearInFlight(key: string): void;
  subscribe(key: string, cb: () => void): () => void;
  invalidate(input: RequestInfo | URL, method?: string): void;
  invalidateByEndpoint(endpointKey: string): void;
  invalidateAll(): void;
  destroy(): void;
}

export function createTypedFetchCache(options?: CacheOptions): TypedFetchCache;

Client: createTypedFetchClient

const client = createTypedFetchClient({
  baseUrl: "https://api.example.com",
  config?: Partial<TypedFetchConfig>;
  cache?: TypedFetchCache; // optional — applied to every request
});

const result = await client.fetch("/users/1", undefined, {
  endpointKey: "GET /users/:id",
  cache?, // per-call override
});

Adapter Exports

typed-fetch provides additional adapter entry points for server frameworks:

import { observeResponse } from '@phumudzo/typed-fetch/adapters/generic';
import { typedFetchObserver } from '@phumudzo/typed-fetch/adapters/hono';
import { withTypedFetchObserver } from '@phumudzo/typed-fetch/adapters/next';

observeResponse (generic)

export async function observeResponse(
  endpointKey: string,
  response: Response,
  config?: Partial<TypedFetchConfig>,
): Promise<void>;
  • Observes JSON responses and queues shape observations.
  • Schedules type generation after registry flush.
  • Runs only in NODE_ENV=development.

typedFetchObserver (Hono)

export function typedFetchObserver(
  config?: Partial<TypedFetchConfig>,
): (c: HonoContext, next: () => Promise<void>) => Promise<void>;
  • Hono middleware that observes all route responses.
  • Automatically builds endpoint keys from method + routePath.

withTypedFetchObserver (Next.js)

export function withTypedFetchObserver(
  endpointKey: string,
  handler: (req: Request, ctx?: unknown) => Promise<Response>,
  config?: Partial<TypedFetchConfig>,
): (req: Request, ctx?: unknown) => Promise<Response>;
  • Wraps Next.js App Router handlers.
  • Requires explicit endpointKey (file-system routes are not available at runtime).

Key Type Narrowing Patterns

Status-based narrowing

if (result.status === 200) {
  // result.data is typed for 200
}

Error checking

if (result.status === 0) {
  console.error(result.error);     // Error object
} else if (result.ok) {
  console.log(result.data);        // 2xx typed data
} else {
  console.log(result.status);      // 4xx / 5xx
}

Function return type annotation

import type { TypedFetchResult } from "@phumudzo/typed-fetch";

async function getUser(id: number): Promise<TypedFetchResult<"GET /users/:id">> {
  return typedFetch(`/api/users/${id}`, undefined, { endpointKey: "GET /users/:id" });
}

Next: Result Object

Learn about the Result Object in detail.

On this page