Skip to content

Fetcher Client API

The @ahoo-wang/fetcher package is the foundation of the Fetcher ecosystem. It wraps the native Fetch API with an interceptor-powered middleware pipeline, URL template resolution, timeout handling, and type-safe result extraction.

Source: packages/fetcher/src/fetcher.ts

Fetcher Class

The main HTTP client class. Supports all standard HTTP methods with automatic header merging, timeout resolution, and interceptor chain processing.

Constructor

typescript
new Fetcher(options?: FetcherOptions)

Source: packages/fetcher/src/fetcher.ts:144

FetcherOptions

Configuration interface for creating a Fetcher instance.

PropertyTypeDefaultDescription
baseURLstring''Base URL prepended to all request URLs
headersRequestHeaders{ 'Content-Type': 'application/json' }Default headers for all requests
timeoutnumberundefinedDefault timeout in milliseconds
urlTemplateStyleUrlTemplateStyleUrlTemplateStyle.PathStyle for URL template parameter interpolation
interceptorsInterceptorManagernew InterceptorManager()Custom interceptor manager
validateStatusValidateStatusstatus >= 200 && status < 300Response status validation function

Source: packages/fetcher/src/fetcher.ts:51

Instance Methods

MethodSignatureDescription
fetchfetch<R>(url, request?, options?): Promise<R>Primary request method; returns Response by default
getget<R>(url, request?, options?): Promise<R>GET request (no body)
postpost<R>(url, request?, options?): Promise<R>POST request
putput<R>(url, request?, options?): Promise<R>PUT request
deletedelete<R>(url, request?, options?): Promise<R>DELETE request (no body)
patchpatch<R>(url, request?, options?): Promise<R>PATCH request
headhead<R>(url, request?, options?): Promise<R>HEAD request (no body)
optionsoptions<R>(url, request?, options?): Promise<R>OPTIONS request (no body)
tracetrace<R>(url, request?, options?): Promise<R>TRACE request (no body)
exchangeexchange(request, options?): Promise<FetchExchange>Low-level: returns the full exchange object
requestrequest<R>(request, options?): Promise<R>Low-level: request with custom extractor

Source: packages/fetcher/src/fetcher.ts:206-500

RequestOptions

PropertyTypeDescription
resultExtractorResultExtractor<any>Function to extract result from the exchange
attributesRecord<string, any> | Map<string, any>Shared attributes for interceptor communication

Source: packages/fetcher/src/fetcher.ts:94

FetchRequest and FetchRequestInit

FetchRequestInit

Configuration for individual HTTP requests. Extends the native RequestInit interface.

PropertyTypeDescription
methodHttpMethodHTTP method (GET, POST, etc.)
headersRequestHeadersRequest-specific headers
bodyBodyInit | Record<string, any> | string | nullRequest body (objects auto-serialized to JSON)
timeoutnumberRequest-specific timeout in milliseconds
urlParamsUrlParamsPath and query parameters
abortControllerAbortControllerCustom abort controller for cancellation
signalAbortSignalAbort signal

Source: packages/fetcher/src/fetchRequest.ts:112

FetchRequest

Extends FetchRequestInit with a required url property.

typescript
interface FetchRequest<BODY extends RequestBodyType = RequestBodyType>
  extends FetchRequestInit<BODY> {
  url: string;
}

Source: packages/fetcher/src/fetchRequest.ts:176

UrlParams

PropertyTypeDescription
pathRecord<string, any>Path parameters for URL template substitution ({id} or :id)
queryRecord<string, any>Query string parameters appended after ?

Source: packages/fetcher/src/urlBuilder.ts:27

NamedFetcher

Extends Fetcher with automatic registration in the global FetcherRegistrar.

typescript
const apiFetcher = new NamedFetcher('api', {
  baseURL: 'https://api.example.com',
  timeout: 5000,
});
// Later:
const sameFetcher = fetcherRegistrar.get('api');

Source: packages/fetcher/src/namedFetcher.ts:38

A default fetcher instance is pre-created and exported:

typescript
import { fetcher } from '@ahoo-wang/fetcher';
fetcher.get('/users');

Source: packages/fetcher/src/namedFetcher.ts:89

FetcherRegistrar

Registry for managing multiple named Fetcher instances.

MethodSignatureDescription
registerregister(name, fetcher): voidRegister a fetcher by name
unregisterunregister(name): booleanRemove a registered fetcher
getget(name): Fetcher | undefinedRetrieve a fetcher by name
requiredGetrequiredGet(name): FetcherRetrieve or throw if not found
default (getter)get default(): FetcherGet the default fetcher
default (setter)set default(fetcher)Set the default fetcher
fetchersget fetchers(): Map<string, Fetcher>Copy of all registered fetchers

Source: packages/fetcher/src/fetcherRegistrar.ts:41

InterceptorManager

Manages the three-phase interceptor pipeline: request, response, and error.

PropertyTypeDescription
requestInterceptorRegistryRequest-phase interceptors
responseInterceptorRegistryResponse-phase interceptors
errorInterceptorRegistryError-phase interceptors

InterceptorRegistry Methods

MethodSignatureDescription
useuse(interceptor): booleanAdd an interceptor (by unique name)
ejecteject(name): booleanRemove an interceptor by name
clearclear(): voidRemove all interceptors
interceptintercept(exchange): Promise<void>Execute all interceptors in order

Source: packages/fetcher/src/interceptorManager.ts:48

Result Extractors

Functions that transform a FetchExchange into the desired return type.

ExtractorReturn TypeDescription
ResultExtractors.ExchangeFetchExchangeReturns the full exchange object
ResultExtractors.ResponseResponseReturns the raw Response
ResultExtractors.JsonPromise<any>Parses response as JSON
ResultExtractors.TextPromise<string>Returns response as text
ResultExtractors.BlobPromise<Blob>Returns response as Blob
ResultExtractors.ArrayBufferPromise<ArrayBuffer>Returns response as ArrayBuffer
ResultExtractors.BytesPromise<Uint8Array>Returns response as Uint8Array

Source: packages/fetcher/src/resultExtractor.ts:131

Error Classes

FetcherError

Base error class for all Fetcher errors. Extends Error with cause support and stack trace copying.

Source: packages/fetcher/src/fetcherError.ts:37

ExchangeError

Thrown when the exchange process fails. Contains the full FetchExchange object for debugging.

PropertyTypeDescription
exchangeFetchExchangeThe exchange that caused the error

Source: packages/fetcher/src/fetcherError.ts:86

HttpStatusValidationError

Thrown by ValidateStatusInterceptor when response status fails validation.

Source: packages/fetcher/src/validateStatusInterceptor.ts:27

FetchExchange

The container object that flows through the interceptor chain.

Property / MethodTypeDescription
fetcherFetcherThe originating Fetcher instance
requestFetchRequestThe request configuration
responseResponse | undefinedThe HTTP response (set after fetch)
errorError | undefinedAny error that occurred
attributesMap<string, any>Shared attributes between interceptors
resultExtractorResultExtractor<any>The result extractor function
ensureRequestHeaders()RequestHeadersLazily initializes request headers
ensureRequestUrlParams()Required<UrlParams>Lazily initializes URL params
hasError()booleanWhether an error is present
hasResponse()booleanWhether a response is present
requiredResponseResponseGets response or throws ExchangeError
extractResult<R>()Promise<R>Applies result extractor (cached)

Source: packages/fetcher/src/fetchExchange.ts:105

Request Flow Diagram

mermaid
flowchart TD
    A["fetcher.get('/users/{id}', { urlParams: { path: { id: 1 } } })"] --> B["Fetcher.resolveExchange()"]
    B --> C["Merge headers: fetcher.headers + request.headers"]
    C --> D["Resolve timeout: request.timeout || fetcher.timeout"]
    D --> E["Create FetchExchange"]
    E --> F["InterceptorManager.exchange()"]
    F --> G["Request Interceptors"]
    G --> G1["RequestBodyInterceptor: serialize body"]
    G1 --> G2["UrlResolveInterceptor: build URL"]
    G2 --> G3["FetchInterceptor: execute fetch()"]
    G3 --> H{"Response OK?"}
    H -->|Yes| I["Response Interceptors"]
    I --> I1["ValidateStatusInterceptor: check status"]
    H -->|No| J["Error Interceptors"]
    I1 -->|Invalid| J
    I1 -->|Valid| K["Return FetchExchange"]
    J --> J1{"Error handled?"}
    J1 -->|Yes| K
    J1 -->|No| L["Throw ExchangeError"]
    K --> M["exchange.extractResult()"]
    M --> N["Return result to caller"]

    style A fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style B fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style E fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style F fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style G fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style I fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style J fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style K fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

UrlBuilder

Constructs complete URLs with path parameter interpolation and query string generation.

typescript
const builder = new UrlBuilder('https://api.example.com', UrlTemplateStyle.UriTemplate);
const url = builder.build('/users/{id}', {
  path: { id: 123 },
  query: { filter: 'active' }
});
// https://api.example.com/users/123?filter=active

Source: packages/fetcher/src/urlBuilder.ts:72

Usage Examples

Basic GET Request

typescript
import { fetcher, ResultExtractors } from '@ahoo-wang/fetcher';

const users = await fetcher.get('/api/users', {}, {
  resultExtractor: ResultExtractors.Json,
});

POST with Body and Path Parameters

typescript
const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });

const response = await fetcher.post('/users', {
  body: { name: 'John', email: 'john@example.com' },
});

Using URL Parameters

typescript
const response = await fetcher.get('/users/{id}/posts', {
  urlParams: {
    path: { id: 123 },
    query: { page: 1, limit: 10 },
  },
});

Custom Interceptors

typescript
const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });

fetcher.interceptors.request.use({
  name: 'AuthInterceptor',
  order: 100,
  intercept(exchange) {
    exchange.request.headers = {
      ...exchange.request.headers,
      Authorization: `Bearer ${getToken()}`,
    };
  },
});

Request Cancellation

typescript
const controller = new AbortController();
const response = await fetcher.get('/long-request', {
  abortController: controller,
});
// Cancel later:
controller.abort();

NamedFetcher Registration

mermaid
classDiagram
    class Fetcher {
        +urlBuilder: UrlBuilder
        +headers: RequestHeaders
        +timeout: number
        +interceptors: InterceptorManager
        +fetch(url, request, options)
        +get(url, request, options)
        +post(url, request, options)
        +exchange(request, options)
    }
    class NamedFetcher {
        +name: string
        +constructor(name, options)
    }
    class FetcherRegistrar {
        -registrar: Map
        +register(name, fetcher)
        +get(name) Fetcher
        +default: Fetcher
        +fetchers: Map
    }

    Fetcher <|-- NamedFetcher
    NamedFetcher --> FetcherRegistrar : "auto-registers"
    FetcherRegistrar --> Fetcher : "manages"

    style Fetcher fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style NamedFetcher fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style FetcherRegistrar fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

Interceptor Registration

mermaid
graph TD
    subgraph sg_1 ["InterceptorManager"]
        direction TB
        subgraph sg_2 ["request: InterceptorRegistry"]
            R1["use(interceptor)"]
            R2["eject(name)"]
            R3["clear()"]
        end
        subgraph sg_3 ["response: InterceptorRegistry"]
            S1["use(interceptor)"]
            S2["eject(name)"]
        end
        subgraph sg_4 ["error: InterceptorRegistry"]
            E1["use(interceptor)"]
            E2["eject(name)"]
        end
    end

    A["Custom Interceptor<br>name, order, intercept()"] --> R1
    A --> S1
    A --> E1

    style R1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style S1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style E1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style A fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

Released under the Apache License 2.0.