Environment Support for upfetch
masterupfetch is compatible with the following environments:
- Browsers (Chrome, Firefox, Safari, Edge)
- Node.js (18.0+)
- Bun
- Deno
- Cloudflare Workers
- Vercel Edge Runtime
repository·master·Indexed 23 days ago
https://github.com/l-blondy/up-fetchAn 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.
upfetch is compatible with the following environments:
up-fetch is compatible with a wide range of JavaScript/TypeScript runtimes, including browsers and modern server-side environments. It supports:
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:
up(fetchFn, getDefaultOptions?) to create a reusable client.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()}`,
},
}))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.
By default, up-fetch handles responses as follows:
null.ResponseError.The reject(response) function determines whether the library executes parseRejected or parseResponse for a given response.
The upfetch library uses a factory pattern to create reusable clients. The core mental model consists of three parts:
up(fetchFn, getDefaultOptions?) creates a reusable client instance.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.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.
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.serializeParams function.upfetch provides hooks for monitoring upload and download progress:
onRequestStreaming: Instruments upload progress.onResponseStreaming: Instruments download progress.Important details for the streaming model:
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.await upfetch('/download', {
onResponseStreaming({ chunk, transferredBytes, totalBytes = transferredBytes }) {
console.log(chunk, transferredBytes, totalBytes)
},
})The execution flow of an up-fetch request follows this specific order:
getDefaultOptions is called.Request object is constructed.onRequest: The request hook runs (can mutate the Request).fetch is executed.onRetry is called if needed.onResponse: Runs exactly once after all retry attempts have finished.reject is run if the request failed.parseRejected or parseResponse is run.schema is validated if provided.onSuccess or onError is run.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')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}`)
},
})To use up-fetch, import the up function and pass the native fetch implementation to it. This creates an enhanced fetch client.
import { up } from 'up-fetch'
export const upfetch = up(fetch)