up-fetch Documentation

repository·master·Indexed 23 days ago

https://github.com/l-blondy/up-fetch

An advanced fetch client builder for TypeScript (v2.6.0) that extends the standard Fetch API. It features automatic body and parameter serialization, built-in retry logic with exponential backoff, lifecycle hooks, and response validation using Zod or Valibot via the Standard Schema Specification. It supports streaming request/response data, custom response parsing, and flexible error handling, including the ability to treat errors as values.

Tokens
17.2K
Snippets
56
Records
92
Agent score
79%

What's inside up-fetch

  1. Supported Environments for up-fetch

    master

    up-fetch is compatible with a wide range of JavaScript/TypeScript runtimes, including browsers and modern server-side environments. It supports:

    • Browsers: Chrome, Firefox, Safari, Edge
    • Runtimes:
      • Node.js (version 18.0 or higher)
      • Bun
      • Deno
    • Edge Runtimes:
      • Cloudflare Workers
      • Vercel Edge Runtime
  2. How to use up-fetch: Mental Model and Core API

    master

    The up-fetch library is designed to create reusable fetch clients that handle request-scoped defaults, automatic shaping, validation, and retries.

    There are two primary ways to interact with the library:

    1. Client Creation: Use up(fetchFn, getDefaultOptions?) to create a reusable client.
    2. Single Request: Use upfetch(input, options?, ctx?) to perform a single request.

    Crucial Concept: When creating a client with up(), the second argument should be a function (getDefaultOptions) rather than a plain object. This function runs on every request, allowing you to compute dynamic values like authentication tokens or timestamps so they stay fresh.

    import { up } from 'up-fetch'
    
    // Creating a reusable client with dynamic defaults
    export const upfetch = up(fetch, () => ({
       baseUrl: 'https://api.example.com',
       headers: {
          Authorization: `Bearer ${readToken()}`,
       },
    }))
  3. Understand timeout behavior

    master

    Timeouts in up-fetch are applied per attempt. If you have multiple retries configured, the timeout duration applies to each individual request attempt rather than the total duration of all attempts combined.

    When using withTimeout(), the library combines the provided signal and timeout using AbortSignal.any (on runtimes that support it). Note that AbortError and TimeoutError are treated as distinct error types.

  4. How upfetch client setup and dynamic defaults work

    master

    The upfetch library uses a factory pattern to create reusable clients. The core mental model consists of three parts:

    1. Client Creation: up(fetchFn, getDefaultOptions?) creates a reusable client instance.
    2. Default Options Factory: The getDefaultOptions(input, fetcherOpts, ctx) function runs automatically for every request made by that client. This allows you to inject logic that determines configuration based on the request input.
    3. Execution: upfetch(input, options?, ctx?) performs a single request using the provided input and options.

    Important Note on Bodies: The default body defined in the client setup is ignored. You must pass the request body directly to the upfetch() call to ensure it is sent.

  5. Understand upfetch merge rules for headers and params

    master

    When making a request, upfetch applies specific merging logic between the default configuration and the individual request options:

    • headers: Default headers are merged with request-specific headers. If a key exists in both, the request values win.
    • params: Default parameters are merged with input URL parameters and request parameters.
    • URL Parameter Preservation: Input URL parameters are preserved and are not passed into the serializeParams function.
  6. How streaming progress works in upfetch

    master

    upfetch provides hooks for monitoring upload and download progress:

    • onRequestStreaming: Instruments upload progress.
    • onResponseStreaming: Instruments download progress.

    Important details for the streaming model:

    • The first callback execution receives an empty chunk and the initial byte counters.
    • The totalBytes value may be undefined if the server does not provide a Content-Length header. To handle this, you can default totalBytes to transferredBytes within the callback.
    • Empty request or response bodies do not guarantee that streaming callbacks will be triggered.
    await upfetch('/download', {
       onResponseStreaming({ chunk, transferredBytes, totalBytes = transferredBytes }) {
          console.log(chunk, transferredBytes, totalBytes)
       },
    })
  7. Understand the up-fetch request lifecycle

    master

    The execution flow of an up-fetch request follows this specific order:

    1. Build defaults: getDefaultOptions is called.
    2. Merge options: Fallback options, defaults, and request-specific options are merged.
    3. Serialization: Request body and headers are serialized.
    4. Construction: URL is resolved and the Request object is constructed.
    5. onRequest: The request hook runs (can mutate the Request).
    6. Execution: The actual fetch is executed.
    7. Retry Logic: The retry policy is evaluated and onRetry is called if needed.
    8. onResponse: Runs exactly once after all retry attempts have finished.
    9. Rejection: reject is run if the request failed.
    10. Parsing: parseRejected or parseResponse is run.
    11. Validation: The schema is validated if provided.
    12. Final Hooks: onSuccess or onError is run.
  8. Handle errors as values using `reject` and `parseResponse`

    master

    By default, upfetch throws a ResponseError when a request fails. To switch to a pattern where errors are returned as values (e.g., { data, error }), set the reject option to return false. When reject returns false, you must provide a parseResponse function to define how both successful and error responses are structured.

    const upfetch = up(fetch, () => ({
       reject: () => false,
       parseResponse: async (response) => {
          const json = await response.json()
          return response.ok
             ? { data: json, error: null }
             : { data: null, error: json }
       },
    }))
    
    // Usage:
    const { data, error } = await upfetch('/users/1')
  9. Stream request and response data

    master

    Upfetch provides hooks for streaming data: onRequestStreaming for uploads and onResponseStreaming for downloads. Both handlers receive an object containing:

    • chunk: Uint8Array: The current chunk of data.
    • transferredBytes: number: Amount of data transferred so far.
    • totalBytes?: number: Total size (from Content-Length or request body).
    // Example: Processing an AI chatbot response stream
    const decoder = new TextDecoder()
    
    upfetch('/ai-chatbot', {
       onResponseStreaming: ({ chunk }) => {
          const text = decoder.decode(chunk, { stream: true })
          console.log(text)
       },
    })
    
    // Example: Upload progress
    upfetch('/upload', {
       method: 'POST',
       body: new File(['large file'], 'foo.txt'),
       onRequestStreaming: ({ transferredBytes, totalBytes }) => {
          console.log(`Progress: ${transferredBytes} / ${totalBytes}`)
       },
    })
    
    // Example: Download progress
    upfetch('/download', {
       onResponseStreaming: ({
          transferredBytes,
          totalBytes = transferredBytes,
       }) => {
          console.log(`Progress: ${transferredBytes} / ${totalBytes}`)
       },
    })